Skip to content

Commit 0722b9c

Browse files
authored
feat(openemr-cmd): pass HOST_UID/HOST_GID through compose to openemr container (#840)
## Summary Completes the cross-repo HOST_UID story started in openemr/openemr#12642 (entrypoint side, already merged + new flex image SHA pinned via dependabot #12644). This PR wires the openemr-devops side: `wt_write_override` emits `HOST_UID`/`HOST_GID` env vars in the openemr service block, sourced from `id -u` / `id -g` at worktree-add / regen time. ## Result For **non-uid=1000 hosts** (CI runners at uid=1001, multi-user dev boxes, distros that start uids at 1001+, macOS via Docker Desktop): apache inside the container now writes bind-mount files with the host's uid. Host-side writes (`git commit`, IDE edits, `worktree regen`, `worktree set-env`) work without any chown dance. The uid-mismatch class of bugs that dominated #833 / #836 / #838 review cycles is eliminated end-to-end. For **uid=1000 hosts** (most Linux desktop developers): zero functional change. The env vars are emitted unconditionally because the entrypoint adoption block is a sub-millisecond no-op on uid=1000 and the consistent behavior across hosts is worth more than the savings. **Backwards compatible:** pre-HOST_UID-aware images silently ignore the env vars. ## CI workarounds removed (now obsolete) Both `sudo chown -R "$(id -u):$(id -g)"` steps in `test-bats-openemr-cmd-real-docker.yml`: 1. **`worktree-lifecycle-e2e`** "One-shot chown back to runner (enables host-side regen + set-env)" — added in #834 2. **`prek-workflow-e2e`** "One-shot chown back to runner (enables host-side writes + git commits)" — added in #836 Both replaced with `NOTE:` comments documenting what used to live there and why the chown is no longer needed — for archaeology by future contributors. ## Kept as back-compat shim The `#833` auto-chown block in `cmd_worktree_remove` stays in place. Inline comment updated to reflect its new status (no-op when the image honors HOST_UID; still useful for users on pre-HOST_UID images and for any drift the entrypoint adoption might miss). ## Tests New bats file `tests/bats/openemr-cmd/host_uid_compose.bats` pins three behaviors: 1. `wt_write_override` emits `HOST_UID` and `HOST_GID` env vars in the override file 2. Values match `id -u` / `id -g` at write time 3. Env vars are correctly nested under `services.openemr.environment` (structural check via yaml.safe_load) 4. Emitted for all three env variants (easy / easy-light / easy-redis) ## Test plan - [ ] BATS openemr-cmd (ubuntu-22.04 + macos-14) pass — new tests + existing suite - [ ] e2e — full worktree lifecycle: passes WITHOUT the sudo chown step (proves apache now adopts runner uid; regen + set-env writes work as runner) - [ ] e2e — prek install + real git commit: passes WITHOUT the sudo chown step (proves `git commit` from the worktree works as runner) - [ ] Other e2e jobs unchanged - [ ] FUNCS_END drift sentinel still passes (no new function added — wt_write_override grew inline, last function still at line 1882) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Generated OpenEMR docker-compose override files now include the current host user and group IDs (`HOST_UID`/`HOST_GID`) in the container environment. * **Bug Fixes** * Worktree lifecycle and pre-install commit workflows no longer require host-side ownership adjustment before regeneration and environment setup. * Permission recovery/chown behavior is more consistent, with backward-compatible fallback for older images or ownership drift. * **Tests** * Added BATS coverage validating `HOST_UID`/`HOST_GID` emission and stability of golden comparisons by masking host-specific values. * Improved BATS harness sourcing for hermetic function-only execution. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 2b62944 commit 0722b9c

14 files changed

Lines changed: 276 additions & 82 deletions

File tree

.github/workflows/test-bats-openemr-cmd-real-docker.yml

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -339,16 +339,12 @@ jobs:
339339
grep -E '^test-e2e[[:space:]]+.*[[:space:]]none[[:space:]]' <<< "${out}" \
340340
|| { echo "FAIL: 'test-e2e' not listed with status=none"; exit 1; }
341341
342-
- name: One-shot chown back to runner (enables host-side regen + set-env)
343-
run: |
344-
# openemr's container startup chowned the whole bind mount to
345-
# apache during install, so host-side host-uid operations
346-
# (regen writing .env, set-env creating new env dir files)
347-
# would fail with EACCES. Chown back to runner once now (stack
348-
# is down, no race). The auto-chown step on the eventual
349-
# `worktree remove` (when stack is back up) will handle any
350-
# further apache-writes between then and now.
351-
sudo chown -R "$(id -u):$(id -g)" "${{ github.workspace }}/openemr-wt-test-e2e"
342+
# NOTE: a `sudo chown -R "$(id -u):$(id -g)" .../openemr-wt-test-e2e`
343+
# step used to live here to recover from apache (uid=1000) chowning
344+
# the bind mount during install. With HOST_UID passthrough now in
345+
# wt_write_override, apache adopts the runner's uid at boot and the
346+
# bind mount stays runner-owned — host-side regen + set-env writes
347+
# work without any chown dance.
352348

353349
- name: "worktree regen: rewrites a stomped .env back to canonical contents"
354350
working-directory: openemr
@@ -1241,21 +1237,12 @@ jobs:
12411237
docker logs "${cname}" --tail 500
12421238
exit 1
12431239
1244-
- name: One-shot chown back to runner (enables host-side writes + git commits)
1245-
run: |
1246-
# openemr's container startup chowns the whole webroot bind
1247-
# mount to apache (uid=1000), but `git commit` runs as the
1248-
# runner (different uid) and needs to write into the working
1249-
# tree (probe file) and update .git/worktrees/prek-e2e/index
1250-
# via `git add`. Chown back now that healthy confirms apache
1251-
# is done with its setup pass. Apache may log a few EACCES
1252-
# warnings on subsequent /meta/health/readyz probes, but the
1253-
# prek tests below don't depend on apache continuing to
1254-
# serve — only on the container being reachable for
1255-
# `docker exec` (which runs as root, sidestepping the
1256-
# bind-mount uid mismatch). The eventual `worktree remove`
1257-
# auto-chown handles the symmetric case for teardown.
1258-
sudo chown -R "$(id -u):$(id -g)" "${{ github.workspace }}/openemr-wt-prek-e2e"
1240+
# NOTE: a `sudo chown -R "$(id -u):$(id -g)" .../openemr-wt-prek-e2e`
1241+
# step used to live here to recover from apache (uid=1000) chowning
1242+
# the bind mount during install. With HOST_UID passthrough now in
1243+
# wt_write_override, apache adopts the runner's uid at boot and the
1244+
# bind mount stays runner-owned — `git commit` from the worktree
1245+
# works without any chown dance.
12591246

12601247
- name: openemr-cmd prek-install (writes shim hooks to shared .git/hooks)
12611248
working-directory: openemr-wt-prek-e2e

tests/bats/openemr-cmd/copy_base_env.bats

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ teardown() {
3131
oc_run_in_funcs() {
3232
local snippet=$1
3333
local script_path=$2
34-
local funcs_end=$3
35-
local tmp_root=$4
34+
local tmp_root=$3
3635
run env OPENEMR_ROOT="${tmp_root}" bash -c "
3736
set -euo pipefail
38-
eval \"\$(head -n ${funcs_end} '${script_path}')\"
37+
__OPENEMR_CMD_SOURCE_FUNCS_ONLY=1
38+
source '${script_path}'
3939
${snippet}
4040
"
4141
}
@@ -52,7 +52,7 @@ oc_file_mode() {
5252

5353
@test "wt_copy_base_env: no-op when source .env is missing" {
5454
oc_run_in_funcs "wt_copy_base_env '${PRIMARY}' '${DEST}'" \
55-
"$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
55+
"$SCRIPT" "$TMP_ROOT"
5656
assert_success
5757
[[ ! -e "${DEST}/.env" ]]
5858
}
@@ -62,7 +62,7 @@ oc_file_mode() {
6262
echo "EVIL=1" > "${TMP_ROOT}/outside"
6363
ln -s "${TMP_ROOT}/outside" "${PRIMARY}/.env"
6464
oc_run_in_funcs "wt_copy_base_env '${PRIMARY}' '${DEST}'" \
65-
"$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
65+
"$SCRIPT" "$TMP_ROOT"
6666
assert_success
6767
[[ ! -e "${DEST}/.env" ]]
6868
}
@@ -71,7 +71,7 @@ oc_file_mode() {
7171
printf 'OPENEMR__ENVIRONMENT=dev\n' > "${PRIMARY}/.env"
7272
chmod 600 "${PRIMARY}/.env"
7373
oc_run_in_funcs "wt_copy_base_env '${PRIMARY}' '${DEST}'" \
74-
"$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
74+
"$SCRIPT" "$TMP_ROOT"
7575
assert_success
7676
assert_output --partial "Copied base .env"
7777
[[ -f "${DEST}/.env" ]]
@@ -84,7 +84,7 @@ oc_file_mode() {
8484
printf 'NEW=1\n' > "${PRIMARY}/.env"
8585
printf 'OLD=1\n' > "${DEST}/.env"
8686
oc_run_in_funcs "wt_copy_base_env '${PRIMARY}' '${DEST}'" \
87-
"$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
87+
"$SCRIPT" "$TMP_ROOT"
8888
assert_success
8989
refute_output --partial "Copied base .env"
9090
grep -q "^OLD=1$" "${DEST}/.env"
@@ -97,7 +97,7 @@ oc_file_mode() {
9797
printf 'NEW=1\n' > "${PRIMARY}/.env"
9898
ln -s "${TMP_ROOT}/should-not-exist" "${DEST}/.env"
9999
oc_run_in_funcs "wt_copy_base_env '${PRIMARY}' '${DEST}'" \
100-
"$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
100+
"$SCRIPT" "$TMP_ROOT"
101101
assert_success
102102
refute_output --partial "Copied base .env"
103103
[[ -L "${DEST}/.env" ]]

tests/bats/openemr-cmd/goldens/easy-light/override.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ volumes:
2727

2828
services:
2929
openemr:
30+
environment:
31+
HOST_UID: "__HOST_UID__"
32+
HOST_GID: "__HOST_GID__"
3033
volumes:
3134
- "__TMP_PARENT__/primary/.git:__TMP_PARENT__/primary/.git:rw"
3235

tests/bats/openemr-cmd/goldens/easy-redis/override.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ volumes:
2929

3030
services:
3131
openemr:
32+
environment:
33+
HOST_UID: "__HOST_UID__"
34+
HOST_GID: "__HOST_GID__"
3235
volumes:
3336
- "__TMP_PARENT__/primary/.git:__TMP_PARENT__/primary/.git:rw"
3437
- "__TMP_PARENT__/openemr-wt-golden-easy-redis/docker/development-easy-redis/php.ini:/usr/local/etc/php/php.ini:ro"

tests/bats/openemr-cmd/goldens/easy/override.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ volumes:
2929

3030
services:
3131
openemr:
32+
environment:
33+
HOST_UID: "__HOST_UID__"
34+
HOST_GID: "__HOST_GID__"
3235
volumes:
3336
- "__TMP_PARENT__/primary/.git:__TMP_PARENT__/primary/.git:rw"
3437

tests/bats/openemr-cmd/helpers.bash

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,30 +72,25 @@ STUB
7272
echo "${d}"
7373
}
7474

75-
# Line number that marks the end of function definitions in the script
76-
# (the last '}' before USAGE_EXIT_CODE=13 and the dispatch logic). Sourcing
77-
# only the first OC_SCRIPT_FUNCS_END lines gives us a library-like view of
78-
# the script: every function defined, no main-line dispatch executed.
79-
# If the script is restructured, bump this constant — the test
80-
# 'OC_SCRIPT_FUNCS_END points at end of function defs' in helpers_pure.bats
81-
# will fail loudly when it drifts.
82-
OC_SCRIPT_FUNCS_END=1882
83-
8475
# Source ONLY the function definitions of openemr-cmd into the current shell.
8576
# Caller is responsible for setting OPENEMR_ROOT (and WT_STATE_FILE / others
8677
# as needed) BEFORE calling, since the script's top-level OPENEMR_ROOT
8778
# assignment runs while sourcing.
8879
#
80+
# Mechanism: set __OPENEMR_CMD_SOURCE_FUNCS_ONLY=1 before sourcing. The
81+
# script has a guard right after the last function definition that
82+
# `return 0`s when the flag is set, short-circuiting the dispatch /
83+
# top-level statements. Replaces an earlier head -n N approach that
84+
# required a magic line-number constant kept in sync with end-of-
85+
# functions via a drift sentinel — broke on every refactor.
86+
#
8987
# Usage (inside a 'bash -c' inside @test):
9088
# oc_source_funcs
9189
# wt_slug feature/foo
9290
oc_source_funcs() {
9391
local script
9492
script=$(oc_script_path) || return 1
95-
# eval, not 'source <(...)': process substitution is broken under macOS
96-
# system bash 3.2, where <() fails to define the sourced functions.
97-
# shellcheck disable=SC2312
98-
eval "$(head -n "${OC_SCRIPT_FUNCS_END}" "${script}")"
93+
__OPENEMR_CMD_SOURCE_FUNCS_ONLY=1 source "${script}"
9994
}
10095

10196
# Initialize $1 as a real git repo with one commit, so commands like

tests/bats/openemr-cmd/helpers_pure.bats

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,45 +30,46 @@ oc_run_in_funcs() {
3030
set -euo pipefail
3131
# eval, not 'source <(...)': process substitution is broken under
3232
# macOS system bash 3.2, where <() fails to define the functions.
33-
eval \"\$(head -n ${funcs_end} '${script_path}')\"
33+
__OPENEMR_CMD_SOURCE_FUNCS_ONLY=1
34+
source '${script_path}'
3435
${snippet}
3536
"
3637
}
3738

3839
# --- wt_slug -----------------------------------------------------------------
3940

4041
@test "wt_slug: slash becomes dash" {
41-
oc_run_in_funcs 'wt_slug feature/foo' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
42+
oc_run_in_funcs 'wt_slug feature/foo' "$SCRIPT" "$TMP_ROOT"
4243
assert_success
4344
assert_output "feature-foo"
4445
}
4546

4647
@test "wt_slug: uppercase is lowercased" {
47-
oc_run_in_funcs 'wt_slug UPPER' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
48+
oc_run_in_funcs 'wt_slug UPPER' "$SCRIPT" "$TMP_ROOT"
4849
assert_success
4950
assert_output "upper"
5051
}
5152

5253
@test "wt_slug: spaces and special chars are stripped" {
53-
oc_run_in_funcs "wt_slug 'with spaces!'" "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
54+
oc_run_in_funcs "wt_slug 'with spaces!'" "$SCRIPT" "$TMP_ROOT"
5455
assert_success
5556
assert_output "withspaces"
5657
}
5758

5859
@test "wt_slug: mixed case + slash + special chars" {
59-
oc_run_in_funcs "wt_slug 'Feature/My-Thing!'" "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
60+
oc_run_in_funcs "wt_slug 'Feature/My-Thing!'" "$SCRIPT" "$TMP_ROOT"
6061
assert_success
6162
assert_output "feature-my-thing"
6263
}
6364

6465
@test "wt_slug: empty input -> empty output (deterministic, not an error)" {
65-
oc_run_in_funcs "wt_slug ''" "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
66+
oc_run_in_funcs "wt_slug ''" "$SCRIPT" "$TMP_ROOT"
6667
assert_success
6768
assert_output ""
6869
}
6970

7071
@test "wt_slug: non-ASCII chars stripped, surrounding ASCII survives" {
71-
oc_run_in_funcs "wt_slug 'résumé'" "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
72+
oc_run_in_funcs "wt_slug 'résumé'" "$SCRIPT" "$TMP_ROOT"
7273
assert_success
7374
# `tr -cd 'a-zA-Z0-9_-'` operates byte-by-byte; the 2-byte UTF-8
7475
# sequences for é (0xc3 0xa9) get dropped, leaving the ASCII letters
@@ -77,7 +78,7 @@ oc_run_in_funcs() {
7778
}
7879

7980
@test "wt_slug: non-ASCII alongside ASCII keeps only the ASCII alnum/_/-" {
80-
oc_run_in_funcs "wt_slug 'foo-bär-baz'" "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
81+
oc_run_in_funcs "wt_slug 'foo-bär-baz'" "$SCRIPT" "$TMP_ROOT"
8182
assert_success
8283
# The ä is dropped; foo and baz survive with dashes intact.
8384
assert_output "foo-br-baz"
@@ -87,78 +88,108 @@ oc_run_in_funcs() {
8788
# tr -cd '[a-zA-Z0-9_-]' allows '-' anywhere including as the first char.
8889
# This test pins the current behavior so anyone trying to harden it
8990
# (e.g., refuse slugs starting with -) updates this test deliberately.
90-
oc_run_in_funcs "wt_slug '-evil'" "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
91+
oc_run_in_funcs "wt_slug '-evil'" "$SCRIPT" "$TMP_ROOT"
9192
assert_success
9293
assert_output "-evil"
9394
}
9495

9596
@test "wt_slug: long input is passed through unchanged (no truncation)" {
9697
local long
9798
long="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
98-
oc_run_in_funcs "wt_slug '${long}'" "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
99+
oc_run_in_funcs "wt_slug '${long}'" "$SCRIPT" "$TMP_ROOT"
99100
assert_success
100101
assert_output "${long}"
101102
}
102103

103104
# --- wt_compose_subdir -------------------------------------------------------
104105

105106
@test "wt_compose_subdir: easy" {
106-
oc_run_in_funcs 'wt_compose_subdir easy' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
107+
oc_run_in_funcs 'wt_compose_subdir easy' "$SCRIPT" "$TMP_ROOT"
107108
assert_success
108109
assert_output "docker/development-easy"
109110
}
110111

111112
@test "wt_compose_subdir: easy-light" {
112-
oc_run_in_funcs 'wt_compose_subdir easy-light' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
113+
oc_run_in_funcs 'wt_compose_subdir easy-light' "$SCRIPT" "$TMP_ROOT"
113114
assert_success
114115
assert_output "docker/development-easy-light"
115116
}
116117

117118
@test "wt_compose_subdir: easy-redis" {
118-
oc_run_in_funcs 'wt_compose_subdir easy-redis' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
119+
oc_run_in_funcs 'wt_compose_subdir easy-redis' "$SCRIPT" "$TMP_ROOT"
119120
assert_success
120121
assert_output "docker/development-easy-redis"
121122
}
122123

123124
# --- wt_validate_env ---------------------------------------------------------
124125

125126
@test "wt_validate_env: accepts easy" {
126-
oc_run_in_funcs 'wt_validate_env easy && echo ok' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
127+
oc_run_in_funcs 'wt_validate_env easy && echo ok' "$SCRIPT" "$TMP_ROOT"
127128
assert_success
128129
assert_output "ok"
129130
}
130131

131132
@test "wt_validate_env: accepts easy-light" {
132-
oc_run_in_funcs 'wt_validate_env easy-light && echo ok' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
133+
oc_run_in_funcs 'wt_validate_env easy-light && echo ok' "$SCRIPT" "$TMP_ROOT"
133134
assert_success
134135
assert_output "ok"
135136
}
136137

137138
@test "wt_validate_env: accepts easy-redis" {
138-
oc_run_in_funcs 'wt_validate_env easy-redis && echo ok' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
139+
oc_run_in_funcs 'wt_validate_env easy-redis && echo ok' "$SCRIPT" "$TMP_ROOT"
139140
assert_success
140141
assert_output "ok"
141142
}
142143

143144
@test "wt_validate_env: rejects bogus env with error message" {
144-
oc_run_in_funcs 'wt_validate_env bogus 2>&1' "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
145+
oc_run_in_funcs 'wt_validate_env bogus 2>&1' "$SCRIPT" "$TMP_ROOT"
145146
assert_failure
146147
assert_output --partial "Invalid env 'bogus'"
147148
}
148149

149150
@test "wt_validate_env: rejects empty env" {
150-
oc_run_in_funcs "wt_validate_env '' 2>&1" "$SCRIPT" "${OC_SCRIPT_FUNCS_END}" "$TMP_ROOT"
151+
oc_run_in_funcs "wt_validate_env '' 2>&1" "$SCRIPT" "$TMP_ROOT"
151152
assert_failure
152153
}
153154

154-
# --- oc_funcs_source_line is current ----------------------------------------
155-
# If someone restructures openemr-cmd and our OC_SCRIPT_FUNCS_END constant
156-
# drifts (e.g. someone adds another function above the dispatch), this test
157-
# catches it loudly: line OC_SCRIPT_FUNCS_END+1 must be 'USAGE_EXIT_CODE=13'
158-
# or very close to it, signalling the end of the function-defs section.
159-
160-
@test "OC_SCRIPT_FUNCS_END points at end of function defs (USAGE_EXIT_CODE follows)" {
161-
run sed -n "$((OC_SCRIPT_FUNCS_END+1)),$((OC_SCRIPT_FUNCS_END+3))p" "$SCRIPT"
155+
# --- source guard for functions-only sourcing -------------------------------
156+
# Replaces the previous OC_SCRIPT_FUNCS_END line-counter drift sentinel.
157+
# The script now has a __OPENEMR_CMD_SOURCE_FUNCS_ONLY=1 guard right
158+
# after the last function def that returns from sourcing before any
159+
# dispatch runs. This test pins both ends of the contract: the guard
160+
# is present in the script AND it actually short-circuits sourcing.
161+
162+
@test "source-funcs-only guard: sourcing with the flag set defines functions but skips dispatch" {
163+
# Sourcing the script with the flag set should define wt_slug
164+
# (a function) without printing the version banner / running any
165+
# other dispatch-side output. Confirm by capturing the return-
166+
# from-source state: wt_slug works; the script didn't run main
167+
# dispatch (no USAGE_EXIT_CODE print, no docker probe, etc.).
168+
run env OPENEMR_ROOT="${TMP_ROOT:-/tmp}" bash -c "
169+
set -euo pipefail
170+
__OPENEMR_CMD_SOURCE_FUNCS_ONLY=1
171+
source '$SCRIPT'
172+
# Sourcing returned cleanly; wt_slug defined and callable.
173+
wt_slug 'feature/foo'
174+
"
162175
assert_success
163-
assert_output --partial "USAGE_EXIT_CODE="
176+
assert_output "feature-foo"
177+
}
178+
179+
@test "source-funcs-only guard: direct execution is unaffected by the flag (script runs normally)" {
180+
# Without the env var, direct execution must work as before:
181+
# --version exits with VERSION_EXIT_CODE (14) and prints the
182+
# canonical "openemr-cmd <ver>" banner.
183+
#
184+
# Note: the script's docker-availability check (DOCKER_CODE=16)
185+
# runs BEFORE --version parsing — macOS GH-hosted runners don't
186+
# have docker installed, so we stub it via the same helper the
187+
# other hermetic tests use. The stub satisfies `command -v docker`
188+
# without invoking real docker.
189+
local stub_dir
190+
stub_dir=$(oc_make_docker_stub_dir)
191+
run env PATH="${stub_dir}:${PATH}" "$SCRIPT" --version
192+
rm -rf "${stub_dir}"
193+
[[ "${status}" -eq 14 ]] || fail "expected exit 14 (VERSION_EXIT_CODE), got ${status}"
194+
[[ "${output}" =~ ^openemr-cmd[[:space:]] ]] || fail "expected version banner, got: ${output}"
164195
}

0 commit comments

Comments
 (0)