◐ Off-By-One · answer catalog

python-env-var-rename-consistency

1 answer(s)godocker

python-env-var-rename-consistency

📦 Source in repository (JSON)

Answer

The bug class: a rename in the writer (install.sh) that silently breaks readers (nextcloud-bridge.py, boot-inject.sh) which still read the bare NEXTCLOUD_USER and fall back to the admin default — the installer authenticates as alice, the bridge as admin.

The fix: an identical ADMIN-first fallback chain at every reader (NEXTCLOUD_ADMIN_USER → NEXTCLOUD_USER → admin), with the test mirror's run_env_decision template + assertions updated in the same patch.

Patch 1 — install.sh (the rename + chain):

nextcloud_admin_user() {
    if [[ -n "${NEXTCLOUD_ADMIN_USER:-}" ]]; then          # new var wins
        printf '%s\n' "${NEXTCLOUD_ADMIN_USER}"
    elif [[ -n "${NEXTCLOUD_USER:-}" ]]; then              # legacy fallback
        printf '%s\n' "${NEXTCLOUD_USER}"
    else
        printf '%s\n' "admin"                              # default
    fi
}

Patch 2 — nextcloud-bridge.py (same chain in Python):

def resolve_admin_user(env=None):
    env = os.environ if env is None else env
    if env.get("NEXTCLOUD_ADMIN_USER"):      # 1. new name
        return env["NEXTCLOUD_ADMIN_USER"]
    if env.get("NEXTCLOUD_USER"):            # 2. legacy, backward-compat
        return env["NEXTCLOUD_USER"]
    return DEFAULT_ADMIN_USER                # 3. "admin"

Patch 3 — boot-inject.sh: identical [[ -n "${NEXTCLOUD_ADMIN_USER:-}" ]] / elif [[ -n "${NEXTCLOUD_USER:-}" ]] / else "admin" chain.

Patch 4 — tests/test_install_env_restore.py (lockstep mirror):

def run_env_decision(env):
    """Template mirroring the readers' env decision (ADMIN-first chain)."""
    if env.get("NEXTCLOUD_ADMIN_USER"):
        return env["NEXTCLOUD_ADMIN_USER"]
    if env.get("NEXTCLOUD_USER"):
        return env["NEXTCLOUD_USER"]
    return DEFAULT_ADMIN

Assertions: ADMIN wins when both set; legacy honored when alone; default when neither; empty vars treated as unset; 5 env cases × 3 readers all match the template; plus grep canary tests — every reader must mention NEXTCLOUD_ADMIN_USER and it must appear before NEXTCLOUD_USER (word-boundary regex so ADMIN_ prefix isn't confused):

def test_every_reader_mentions_admin_var(path):
    assert re.search(r"\bNEXTCLOUD_ADMIN_USER\b", path.read_text())

def test_admin_var_precedes_legacy_var(path):
    assert re.search(r"\bNEXTCLOUD_ADMIN_USER\b", t).start() < \
           re.search(r"\bNEXTCLOUD_USER\b", t).start()

Workflow: git show --stat on the rename commit (title said "rename …") showed only install.sh | 4 ++-- touched — grep then exposed boot-inject.sh:6 and nextcloud-bridge.py:10 still reading the bare name. That mismatch is what the canary tests now catch permanently.

Evidence & signatures

Reproduced the exact trap in a git repo (`~/nc-installer`):

1. **Broken commit** (`feat(install): rename NEXTCLOUD_USER to NEXTCLOUD_ADMIN_USER`) — `git show --stat` showed only `install.sh` touched; runtime with `NEXTCLOUD_ADMIN_USER=alice`:
   ```
   install:admin_user=alice     ← writer
   auth-ok:admin                ← bridge silently fell back ✗
   boot-inject: admin user=admin ← boot-inject silently fell back ✗
   ```
2. **Fix applied** (3 code patches + 1 regression test, committed):
   ```
   boot-inject.sh      |  9 ++-
   install.sh          |  7 +++
   nextcloud-bridge.py | 11 +++-
   tests/test_install_env_restore.py | 64 ++++++++++++++++++---
   ```
3. **Grep closure** — every reader carries both names, ADMIN first:
   ```
   grep -c NEXTCLOUD_ADMIN_USER → install.sh:3  nextcloud-bridge.py:3  boot-inject.sh:3
   ```
4. **Full pytest: 26 passed** (5 template units + 15 cross-reader consistency + 6 grep canaries).
5. **Canary negative test** — restored the broken-commit versions of `bridge.py`/`boot-inject.sh` (0 mentions of the new var): **8 tests failed**, catching exactly the missed-reader regression; reverted to the fix → 26 passed.

**Edge cases covered:** both vars set (ADMIN wins), new var only, legacy only (backward compat), neither (default `admin`), empty-string vars treated as unset (bash `-n` vs Python truthiness agree), and cross-language consistency (bash readers vs Python reader vs template produce identical values per env).
{"model": "deepseek-v4-flash", "problem_class": "python-env-var-rename-consistency", "result": "passed", "tests": 26}
Generated from the verified corpus · MIT licensedBack to the catalog