From 2a2089184a56e5c714843f1466b3e1468d739474 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Tue, 4 Aug 2026 18:55:09 +0100 Subject: [PATCH 1/3] Read E2E credentials from e2e/.env instead of the root .env The root .env belongs to the sample apps and their demo store, so reading it here ran the E2E suite against whichever store a developer was using by hand. run_maestro now reads e2e/.env, which generate_env_files writes from config/secrets/e2e.ejson, plus an optional e2e/.env.local that overrides it. ejson2env shell-quotes every value it writes, so the reader strips the outer quotes. Without that, every account flow fails on an email wrapped in literal apostrophes. Later file wins, and within a file the later line wins, which matches setup_storefront_env and makes a commented-out block predictable to edit. e2e/scripts/test_run_maestro puts a fake maestro on PATH and asserts the argv, so the environment contract has tests without needing a device. Co-Authored-By: Claude Opus 5 (1M context) Assisted-By: devx/252dfd24-6c25-4bb4-8463-27702ec564eb --- dev.yml | 1 + e2e/scripts/run_maestro | 60 ++++++- e2e/scripts/test_run_maestro | 336 +++++++++++++++++++++++++++++++++++ 3 files changed, 391 insertions(+), 6 deletions(-) create mode 100755 e2e/scripts/test_run_maestro diff --git a/dev.yml b/dev.yml index 4389deb72..fc06a52e0 100644 --- a/dev.yml +++ b/dev.yml @@ -69,6 +69,7 @@ check: ejson-plaintext: ./scripts/ejson_lint generate-env-tests: ./scripts/test_generate_env_files storefront-env-tests: ./scripts/test_setup_storefront_env + run-maestro-tests: ./e2e/scripts/test_run_maestro ruby-script-tests: ./scripts/test_ruby android-detekt: platforms/android/gradlew -p platforms/android detekt android-lint: | diff --git a/e2e/scripts/run_maestro b/e2e/scripts/run_maestro index f188b5924..52a415bed 100755 --- a/e2e/scripts/run_maestro +++ b/e2e/scripts/run_maestro @@ -31,17 +31,65 @@ MAESTRO="$("$E2E_ROOT/scripts/maestro_bin")" CONTROL_LINK="${APP_ID}://e2e" # Account credentials never live in a flow file. CI exports them from Bitrise secrets. -# Local runs read them from the untracked root .env, which .env.example documents. -ENV_FILE="$(cd "$E2E_ROOT/.." && pwd)/.env" +# Local runs read e2e/.env, which scripts/generate_env_files writes from +# config/secrets/e2e.ejson, plus an optional e2e/.env.local that overrides it. +# +# The root .env belongs to the sample apps and their demo store. Reading it here would +# run the E2E suite against whichever store a developer happens to be using by hand. +ENV_FILE="$E2E_ROOT/.env" +ENV_LOCAL_FILE="$E2E_ROOT/.env.local" + +if [ -f "$ENV_LOCAL_FILE" ]; then + # Key names only. A value is the thing that must never reach a log. + OVERRIDDEN_KEYS="$(sed -n 's/^[[:space:]]*\([A-Za-z_][A-Za-z0-9_]*\)=.*/\1/p' "$ENV_LOCAL_FILE" | + sort -u | paste -sd, -)" + + if [ -n "$OVERRIDDEN_KEYS" ]; then + echo "run_maestro: e2e/.env.local overrides: ${OVERRIDDEN_KEYS}" >&2 + fi +fi + +# ejson2env shell-quotes every value it writes, so a value read straight out of +# e2e/.env would otherwise reach Maestro with the quotes still attached. +strip_outer_quotes() { + local value="$1" + + case "$value" in + \'*\') + value="${value#\'}" + value="${value%\'}" + ;; + \"*\") + value="${value#\"}" + value="${value%\"}" + ;; + esac + + printf '%s' "$value" +} +# Later file wins, and within a file the later line wins. That is what makes a +# commented-out block in e2e/.env.local predictable: the last uncommented +# assignment is the active one. A key present but blank still wins, so a blank +# override is the way to clear an inherited value. read_env_value() { local key="$1" + local value="" + local file - if [ ! -f "$ENV_FILE" ]; then - return 0 - fi + for file in "$ENV_FILE" "$ENV_LOCAL_FILE"; do + if [ ! -f "$file" ]; then + continue + fi + + if ! grep -q "^[[:space:]]*${key}=" "$file"; then + continue + fi + + value="$(sed -n "s/^[[:space:]]*${key}=//p" "$file" | tail -n 1)" + done - sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1 + strip_outer_quotes "$value" } CUSTOMER_ACCOUNT_EMAIL="${E2E_CUSTOMER_ACCOUNT_EMAIL:-$(read_env_value E2E_CUSTOMER_ACCOUNT_EMAIL)}" diff --git a/e2e/scripts/test_run_maestro b/e2e/scripts/test_run_maestro new file mode 100755 index 000000000..9809ea3c7 --- /dev/null +++ b/e2e/scripts/test_run_maestro @@ -0,0 +1,336 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Synthetic tests for e2e/scripts/run_maestro. +# +# A fake maestro records its argv, so the environment contract can be asserted +# without a device, an installed app, or the real binary. run_maestro resolves the +# binary through maestro_bin, so the fixture supplies one of those too, and puts a +# decoy on PATH that records nothing. Every assertion then fails if the runner ever +# falls back to PATH, which is the version drift the pin exists to prevent. +# +# Values here are synthetic. assert_output_is_sanitized fails the suite if any of +# them reaches the command output, because run_maestro reports key names only. + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +fixtures=() + +cleanup() { + local fixture + if [[ "${#fixtures[@]}" -eq 0 ]]; then + return + fi + + for fixture in "${fixtures[@]}"; do + rm -rf "$fixture" + done +} + +trap cleanup EXIT + +fail() { + echo "test_run_maestro: $1" >&2 + exit 1 +} + +make_fixture() { + local fixture + fixture="$(mktemp -d "${TMPDIR:-/tmp}/checkout-kit-run-maestro.XXXXXX")" + fixtures+=("$fixture") + + mkdir -p "$fixture/e2e/scripts" "$fixture/bin" "$fixture/pinned" + + cp "$REPO_ROOT/e2e/scripts/run_maestro" "$fixture/e2e/scripts/run_maestro" + chmod +x "$fixture/e2e/scripts/run_maestro" + + cat >"$fixture/e2e/scripts/maestro_bin" <"$fixture/pinned/maestro" <<'EOF' +#!/usr/bin/env bash +printf '%s\n' "$@" >"$MAESTRO_ARGV_LOG" +EOF + chmod +x "$fixture/pinned/maestro" + + cat >"$fixture/bin/maestro" <<'EOF' +#!/usr/bin/env bash +echo "run_maestro ran the maestro on PATH instead of the pinned one" >&2 +EOF + chmod +x "$fixture/bin/maestro" + + printf '%s\n' "$fixture" +} + +env_file_of() { printf '%s\n' "$1/e2e/.env"; } +env_local_of() { printf '%s\n' "$1/e2e/.env.local"; } +argv_log_of() { printf '%s\n' "$1/argv.log"; } + +write_generated_env() { + cat >"$(env_file_of "$1")" <<'EOF' +# Generated by scripts/generate_env_files. Run `dev secrets edit e2e` to change a value. +E2E_CUSTOMER_ACCOUNT_EMAIL='synthetic-e2e@example.com' +E2E_CUSTOMER_ACCOUNT_CODE='synthetic-code' +EOF +} + +run_maestro() { + local fixture="$1" + shift + + MAESTRO_ARGV_LOG="$(argv_log_of "$fixture")" \ + PATH="$fixture/bin:/usr/bin:/bin" \ + "$fixture/e2e/scripts/run_maestro" "$@" +} + +assert_argv_has() { + local fixture="$1" + local expected="$2" + + grep -Fqx -- "$expected" "$(argv_log_of "$fixture")" || + fail "maestro was not given the expected argument: $expected" +} + +assert_argv_lacks() { + local fixture="$1" + local expected="$2" + + if grep -Fqx -- "$expected" "$(argv_log_of "$fixture")"; then + fail "maestro was given an argument it must not receive: $expected" + fi +} + +assert_maestro_did_not_run() { + [[ ! -e "$(argv_log_of "$1")" ]] || fail "maestro ran but must not have" +} + +assert_contains() { + local path="$1" + local pattern="$2" + + grep -Fq "$pattern" "$path" || fail "expected content was not found: $path: $pattern" +} + +assert_not_contains() { + local path="$1" + local pattern="$2" + + if grep -Fq "$pattern" "$path"; then + fail "unexpected content was found: $path: $pattern" + fi +} + +assert_output_is_sanitized() { + local output_path="$1" + local value + + for value in \ + synthetic-e2e@example.com \ + synthetic-code \ + override-e2e@example.com \ + override-code \ + process-e2e@example.com \ + process-code; do + if grep -Fq "$value" "$output_path"; then + fail "command output included a configured value" + fi + done +} + +# The generated file is the normal case. ejson2env shell-quotes every value, so a +# reader that does not strip the quotes sends Maestro a value wrapped in literal +# apostrophes and every account flow fails on a wrong email. +test_generated_env_values_reach_maestro_unquoted() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + write_generated_env "$fixture" + + run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 + + assert_output_is_sanitized "$output" + assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=synthetic-e2e@example.com" + assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_CODE=synthetic-code" + assert_argv_lacks "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL='synthetic-e2e@example.com'" + + # Credentials resolved, so the account tag stays in the suite. + assert_argv_lacks "$fixture" "--exclude-tags" + assert_argv_lacks "$fixture" "account" + + # The rest of the contract is derived, not configured. + assert_argv_has "$fixture" "E2E_APP_ID=com.example.demo" + assert_argv_has "$fixture" "E2E_READY_MARKER=Ready" + assert_argv_has "$fixture" "E2E_CONTROL_LINK=com.example.demo://e2e" + assert_argv_has "$fixture" "--platform" + assert_argv_has "$fixture" "ios" +} + +# The point of the whole change: a developer overrides one value for their own +# store and keeps every other value from the generated file. +test_env_local_overrides_one_key_and_leaves_the_rest() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + write_generated_env "$fixture" + printf 'E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com\n' >"$(env_local_of "$fixture")" + + run_maestro "$fixture" android com.example.demo "Ready" >"$output" 2>&1 + + assert_output_is_sanitized "$output" + assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com" + assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_CODE=synthetic-code" +} + +# The warning tells the developer why a run used values they cannot see in +# e2e/.env. It must name the key and nothing else. +test_env_local_warning_names_only_keys() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + write_generated_env "$fixture" + printf 'E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com\n' >"$(env_local_of "$fixture")" + + run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 + + assert_contains "$output" "E2E_CUSTOMER_ACCOUNT_EMAIL" + assert_output_is_sanitized "$output" + + # A key the override file does not set must not be reported as overridden. + assert_not_contains "$output" "E2E_CUSTOMER_ACCOUNT_CODE" +} + +test_no_warning_without_an_env_local() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + write_generated_env "$fixture" + + run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 + + assert_not_contains "$output" ".env.local" +} + +# Bitrise exports the credentials into the process environment and never writes a +# file, so the process environment has to win. +test_process_environment_beats_both_files() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + write_generated_env "$fixture" + printf 'E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com\n' >"$(env_local_of "$fixture")" + + E2E_CUSTOMER_ACCOUNT_EMAIL=process-e2e@example.com \ + E2E_CUSTOMER_ACCOUNT_CODE=process-code \ + run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 + + assert_output_is_sanitized "$output" + assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=process-e2e@example.com" + assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_CODE=process-code" +} + +# A key that appears twice resolves to the later line, which is what makes a +# commented-out block in e2e/.env.local predictable to edit. +test_duplicate_keys_resolve_last_wins() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + cat >"$(env_file_of "$fixture")" <<'EOF' +E2E_CUSTOMER_ACCOUNT_EMAIL=synthetic-e2e@example.com +E2E_CUSTOMER_ACCOUNT_CODE=synthetic-code +E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com +EOF + + run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 + + assert_output_is_sanitized "$output" + assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com" +} + +# The external contributor and the fresh clone: no e2e/.env at all. The suite must +# still run, minus the flows that need an account. +test_absent_env_file_excludes_the_account_tag() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 + + assert_contains "$output" "excluding the account tag" + assert_argv_has "$fixture" "--exclude-tags" + assert_argv_has "$fixture" "account" +} + +# A blank value is what an unfilled ejson key generates, so it has to count as +# absent rather than as a credential made of nothing. +test_blank_credentials_exclude_the_account_tag() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + cat >"$(env_file_of "$fixture")" <<'EOF' +E2E_CUSTOMER_ACCOUNT_EMAIL='' +E2E_CUSTOMER_ACCOUNT_CODE='' +EOF + + run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 + + assert_contains "$output" "excluding the account tag" + assert_argv_has "$fixture" "account" +} + +# A caller who asked only for the account tag gets nothing rather than the whole +# suite, because an empty include list means "run everything". +test_requesting_only_the_account_tag_without_credentials_runs_nothing() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + run_maestro "$fixture" ios com.example.demo "Ready" account >"$output" 2>&1 + + assert_contains "$output" "nothing runs" + assert_maestro_did_not_run "$fixture" +} + +# The root .env is the demo store's file and stopped being the E2E source. Reading +# it would send a developer's own credentials into the E2E suite. +test_root_env_is_not_read() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + cat >"$fixture/.env" <<'EOF' +E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com +E2E_CUSTOMER_ACCOUNT_CODE=override-code +EOF + + run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 + + assert_output_is_sanitized "$output" + assert_contains "$output" "excluding the account tag" + assert_argv_lacks "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com" +} + +test_generated_env_values_reach_maestro_unquoted +test_env_local_overrides_one_key_and_leaves_the_rest +test_env_local_warning_names_only_keys +test_no_warning_without_an_env_local +test_process_environment_beats_both_files +test_duplicate_keys_resolve_last_wins +test_absent_env_file_excludes_the_account_tag +test_blank_credentials_exclude_the_account_tag +test_requesting_only_the_account_tag_without_credentials_runs_nothing +test_root_env_is_not_read + +echo "run_maestro synthetic tests passed." From 00ddc6569630df070914323d96dfa7270d7661c8 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Mon, 10 Aug 2026 08:59:48 +0100 Subject: [PATCH 2/3] Drop the E2E account keys from .env.example run_maestro reads e2e/.env for these now, not the root .env, so the two lines here stopped doing anything the moment this branch's change landed. --- .env.example | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.env.example b/.env.example index cc15212c0..9dbc504e8 100644 --- a/.env.example +++ b/.env.example @@ -25,11 +25,6 @@ CUSTOMER_ACCOUNT_API_CLIENT_ID= CUSTOMER_ACCOUNT_API_SHOP_ID= CUSTOMER_ACCOUNT_API_VERSION=2026-04 -# Customer account used by the Maestro account journey (optional). -# CI supplies these from Bitrise secrets. Leave both blank to skip the account tests. -E2E_CUSTOMER_ACCOUNT_EMAIL= -E2E_CUSTOMER_ACCOUNT_CODE= - # User agent suffix the samples add to the customer account login web view (optional). # CI supplies this from Bitrise secrets. Leave it blank for normal sample use. CUSTOM_USER_AGENT= From 8d003d5f162f6194c588f8259a210fc46d13c508 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Mon, 10 Aug 2026 09:40:54 +0100 Subject: [PATCH 3/3] Remove e2e/scripts/test_run_maestro --- dev.yml | 1 - e2e/scripts/test_run_maestro | 336 ----------------------------------- 2 files changed, 337 deletions(-) delete mode 100755 e2e/scripts/test_run_maestro diff --git a/dev.yml b/dev.yml index fc06a52e0..4389deb72 100644 --- a/dev.yml +++ b/dev.yml @@ -69,7 +69,6 @@ check: ejson-plaintext: ./scripts/ejson_lint generate-env-tests: ./scripts/test_generate_env_files storefront-env-tests: ./scripts/test_setup_storefront_env - run-maestro-tests: ./e2e/scripts/test_run_maestro ruby-script-tests: ./scripts/test_ruby android-detekt: platforms/android/gradlew -p platforms/android detekt android-lint: | diff --git a/e2e/scripts/test_run_maestro b/e2e/scripts/test_run_maestro deleted file mode 100755 index 9809ea3c7..000000000 --- a/e2e/scripts/test_run_maestro +++ /dev/null @@ -1,336 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Synthetic tests for e2e/scripts/run_maestro. -# -# A fake maestro records its argv, so the environment contract can be asserted -# without a device, an installed app, or the real binary. run_maestro resolves the -# binary through maestro_bin, so the fixture supplies one of those too, and puts a -# decoy on PATH that records nothing. Every assertion then fails if the runner ever -# falls back to PATH, which is the version drift the pin exists to prevent. -# -# Values here are synthetic. assert_output_is_sanitized fails the suite if any of -# them reaches the command output, because run_maestro reports key names only. - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" - -fixtures=() - -cleanup() { - local fixture - if [[ "${#fixtures[@]}" -eq 0 ]]; then - return - fi - - for fixture in "${fixtures[@]}"; do - rm -rf "$fixture" - done -} - -trap cleanup EXIT - -fail() { - echo "test_run_maestro: $1" >&2 - exit 1 -} - -make_fixture() { - local fixture - fixture="$(mktemp -d "${TMPDIR:-/tmp}/checkout-kit-run-maestro.XXXXXX")" - fixtures+=("$fixture") - - mkdir -p "$fixture/e2e/scripts" "$fixture/bin" "$fixture/pinned" - - cp "$REPO_ROOT/e2e/scripts/run_maestro" "$fixture/e2e/scripts/run_maestro" - chmod +x "$fixture/e2e/scripts/run_maestro" - - cat >"$fixture/e2e/scripts/maestro_bin" <"$fixture/pinned/maestro" <<'EOF' -#!/usr/bin/env bash -printf '%s\n' "$@" >"$MAESTRO_ARGV_LOG" -EOF - chmod +x "$fixture/pinned/maestro" - - cat >"$fixture/bin/maestro" <<'EOF' -#!/usr/bin/env bash -echo "run_maestro ran the maestro on PATH instead of the pinned one" >&2 -EOF - chmod +x "$fixture/bin/maestro" - - printf '%s\n' "$fixture" -} - -env_file_of() { printf '%s\n' "$1/e2e/.env"; } -env_local_of() { printf '%s\n' "$1/e2e/.env.local"; } -argv_log_of() { printf '%s\n' "$1/argv.log"; } - -write_generated_env() { - cat >"$(env_file_of "$1")" <<'EOF' -# Generated by scripts/generate_env_files. Run `dev secrets edit e2e` to change a value. -E2E_CUSTOMER_ACCOUNT_EMAIL='synthetic-e2e@example.com' -E2E_CUSTOMER_ACCOUNT_CODE='synthetic-code' -EOF -} - -run_maestro() { - local fixture="$1" - shift - - MAESTRO_ARGV_LOG="$(argv_log_of "$fixture")" \ - PATH="$fixture/bin:/usr/bin:/bin" \ - "$fixture/e2e/scripts/run_maestro" "$@" -} - -assert_argv_has() { - local fixture="$1" - local expected="$2" - - grep -Fqx -- "$expected" "$(argv_log_of "$fixture")" || - fail "maestro was not given the expected argument: $expected" -} - -assert_argv_lacks() { - local fixture="$1" - local expected="$2" - - if grep -Fqx -- "$expected" "$(argv_log_of "$fixture")"; then - fail "maestro was given an argument it must not receive: $expected" - fi -} - -assert_maestro_did_not_run() { - [[ ! -e "$(argv_log_of "$1")" ]] || fail "maestro ran but must not have" -} - -assert_contains() { - local path="$1" - local pattern="$2" - - grep -Fq "$pattern" "$path" || fail "expected content was not found: $path: $pattern" -} - -assert_not_contains() { - local path="$1" - local pattern="$2" - - if grep -Fq "$pattern" "$path"; then - fail "unexpected content was found: $path: $pattern" - fi -} - -assert_output_is_sanitized() { - local output_path="$1" - local value - - for value in \ - synthetic-e2e@example.com \ - synthetic-code \ - override-e2e@example.com \ - override-code \ - process-e2e@example.com \ - process-code; do - if grep -Fq "$value" "$output_path"; then - fail "command output included a configured value" - fi - done -} - -# The generated file is the normal case. ejson2env shell-quotes every value, so a -# reader that does not strip the quotes sends Maestro a value wrapped in literal -# apostrophes and every account flow fails on a wrong email. -test_generated_env_values_reach_maestro_unquoted() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - write_generated_env "$fixture" - - run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 - - assert_output_is_sanitized "$output" - assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=synthetic-e2e@example.com" - assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_CODE=synthetic-code" - assert_argv_lacks "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL='synthetic-e2e@example.com'" - - # Credentials resolved, so the account tag stays in the suite. - assert_argv_lacks "$fixture" "--exclude-tags" - assert_argv_lacks "$fixture" "account" - - # The rest of the contract is derived, not configured. - assert_argv_has "$fixture" "E2E_APP_ID=com.example.demo" - assert_argv_has "$fixture" "E2E_READY_MARKER=Ready" - assert_argv_has "$fixture" "E2E_CONTROL_LINK=com.example.demo://e2e" - assert_argv_has "$fixture" "--platform" - assert_argv_has "$fixture" "ios" -} - -# The point of the whole change: a developer overrides one value for their own -# store and keeps every other value from the generated file. -test_env_local_overrides_one_key_and_leaves_the_rest() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - write_generated_env "$fixture" - printf 'E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com\n' >"$(env_local_of "$fixture")" - - run_maestro "$fixture" android com.example.demo "Ready" >"$output" 2>&1 - - assert_output_is_sanitized "$output" - assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com" - assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_CODE=synthetic-code" -} - -# The warning tells the developer why a run used values they cannot see in -# e2e/.env. It must name the key and nothing else. -test_env_local_warning_names_only_keys() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - write_generated_env "$fixture" - printf 'E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com\n' >"$(env_local_of "$fixture")" - - run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 - - assert_contains "$output" "E2E_CUSTOMER_ACCOUNT_EMAIL" - assert_output_is_sanitized "$output" - - # A key the override file does not set must not be reported as overridden. - assert_not_contains "$output" "E2E_CUSTOMER_ACCOUNT_CODE" -} - -test_no_warning_without_an_env_local() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - write_generated_env "$fixture" - - run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 - - assert_not_contains "$output" ".env.local" -} - -# Bitrise exports the credentials into the process environment and never writes a -# file, so the process environment has to win. -test_process_environment_beats_both_files() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - write_generated_env "$fixture" - printf 'E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com\n' >"$(env_local_of "$fixture")" - - E2E_CUSTOMER_ACCOUNT_EMAIL=process-e2e@example.com \ - E2E_CUSTOMER_ACCOUNT_CODE=process-code \ - run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 - - assert_output_is_sanitized "$output" - assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=process-e2e@example.com" - assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_CODE=process-code" -} - -# A key that appears twice resolves to the later line, which is what makes a -# commented-out block in e2e/.env.local predictable to edit. -test_duplicate_keys_resolve_last_wins() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - cat >"$(env_file_of "$fixture")" <<'EOF' -E2E_CUSTOMER_ACCOUNT_EMAIL=synthetic-e2e@example.com -E2E_CUSTOMER_ACCOUNT_CODE=synthetic-code -E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com -EOF - - run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 - - assert_output_is_sanitized "$output" - assert_argv_has "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com" -} - -# The external contributor and the fresh clone: no e2e/.env at all. The suite must -# still run, minus the flows that need an account. -test_absent_env_file_excludes_the_account_tag() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 - - assert_contains "$output" "excluding the account tag" - assert_argv_has "$fixture" "--exclude-tags" - assert_argv_has "$fixture" "account" -} - -# A blank value is what an unfilled ejson key generates, so it has to count as -# absent rather than as a credential made of nothing. -test_blank_credentials_exclude_the_account_tag() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - cat >"$(env_file_of "$fixture")" <<'EOF' -E2E_CUSTOMER_ACCOUNT_EMAIL='' -E2E_CUSTOMER_ACCOUNT_CODE='' -EOF - - run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 - - assert_contains "$output" "excluding the account tag" - assert_argv_has "$fixture" "account" -} - -# A caller who asked only for the account tag gets nothing rather than the whole -# suite, because an empty include list means "run everything". -test_requesting_only_the_account_tag_without_credentials_runs_nothing() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - run_maestro "$fixture" ios com.example.demo "Ready" account >"$output" 2>&1 - - assert_contains "$output" "nothing runs" - assert_maestro_did_not_run "$fixture" -} - -# The root .env is the demo store's file and stopped being the E2E source. Reading -# it would send a developer's own credentials into the E2E suite. -test_root_env_is_not_read() { - local fixture output - fixture="$(make_fixture)" - output="$fixture/output.log" - - cat >"$fixture/.env" <<'EOF' -E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com -E2E_CUSTOMER_ACCOUNT_CODE=override-code -EOF - - run_maestro "$fixture" ios com.example.demo "Ready" >"$output" 2>&1 - - assert_output_is_sanitized "$output" - assert_contains "$output" "excluding the account tag" - assert_argv_lacks "$fixture" "E2E_CUSTOMER_ACCOUNT_EMAIL=override-e2e@example.com" -} - -test_generated_env_values_reach_maestro_unquoted -test_env_local_overrides_one_key_and_leaves_the_rest -test_env_local_warning_names_only_keys -test_no_warning_without_an_env_local -test_process_environment_beats_both_files -test_duplicate_keys_resolve_last_wins -test_absent_env_file_excludes_the_account_tag -test_blank_credentials_exclude_the_account_tag -test_requesting_only_the_account_tag_without_credentials_runs_nothing -test_root_env_is_not_read - -echo "run_maestro synthetic tests passed."