Skip to content

fix(tests): keep Dependencies/.env out of the master suite (and fix the loader guard) - #182

Merged
DoRmAmMu1997 merged 2 commits into
mainfrom
fix/sl-hunting-loader-guard
Sep 24, 2026
Merged

DoRmAmMu1997 merged 2 commits into
mainfrom
fix/sl-hunting-loader-guard

Conversation

@DoRmAmMu1997

@DoRmAmMu1997 DoRmAmMu1997 commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

A guard from #181 fails on your machine — and only there

Running the gates on merged main in your checkout: 617 run, 1 failure — test_the_flag_is_set_for_the_load_only, one of the guards #181 added. It found SL_HUNTING_ENABLED='true' where it had been unset before the master loaded.

The loader isn't the cause. It sets the flag inside patch.dict, which restores the environment when the load ends. The value comes back from the next import in the test module: flattrade_execution.py calls load_dotenv() at import time, outside any patch, and re-reads Dependencies/.env. Proven in isolation — importing that one module moves SL_HUNTING_ENABLED from unset to 'true'.

CI and every worktree have no .env, which is why the guard passed on #181's CI.

The fix

Record the value the moment the master's load block ends — before that import — and have the guard (renamed test_the_loader_puts_back_the_flag_it_set) compare that to the pre-load value. It now tests exactly what it claims, and doesn't care about .env loading elsewhere.

Proof

Reproduced without copying any credentials — a throwaway Dependencies/.env containing only SL_HUNTING_ENABLED=true:

old guard new guard
with the one-line .env (your condition) 1 failure 617 OK
without a .env (CI's condition) 617 OK 617 OK

Negative-tested in both conditions: setting the flag for the whole process before the load, and setting it after the load instead of inside patch.dict, are both caught; a control rewording passes. The throwaway .env was removed.

Gates: master 617 OK with and without the one-line .env · 28 market-data-health · 1594 pytest · ruff · mypy (80 files) · compileall.


Second commit: keep Dependencies/.env out of the master suite entirely

The first commit worked around the symptom. This removes the cause: on your machine the master suite ran against your .env; CI, which has none, didn't.

Two leaks, not one. The master, dhan_execution and flattrade_execution all call load_dotenv() at import.

  1. The Flattrade imports ran outside any patch, copying your whole .env — flags and broker credentials — into os.environ for every later test.
  2. The master's load was already inside patch.dict, which restores os.environ — but its load_dotenv still ran, and still let .env values set the master's import-time constants.

Measured without touching your real .env — a throwaway .env holding the 616 key names from env.example, each set to a placeholder:

leaked into os.environ tests that changed
before (this PR's parent) 616 21
Flattrade imports wrapped only 0 12
all three loads isolated 0 0

Of the 21: nine read the environment at run time (broker routing ×4, the live/paper gates, the per-strategy toggle), twelve depended on master constants set at import (websocket feed, strangle re-entry, order correlation). None needs a .env — all pass without one — so none was relying on it; they were exposed to it. They passed on your box only because your values are sane.

The fix is test-side only. Every module-level repository load goes through _isolated_from_dotenv: patch.dict restores os.environ exactly (carrying the values a load needs), and dotenv.load_dotenv is stubbed so the file isn't read at all. Production's load_dotenv is unchanged; no default moves. Follows the precedent in test_index_fetch_construction.py.

Guards — one of which holds in CI, where the others would be blind:

  • behavioural: os.environ snapshotted before the first repo import and after the last; fails naming any variable added/removed/changed — names only, never values, since on your machine a value can be a credential;
  • probe: a throwaway module + .env in a temp dir, loaded with the real load_dotenv — bare, it must pick the value up (the control); isolated, it must not;
  • structural: the master and both Flattrade loads must go through the isolation — so a bare exec_module fails here instead of passing CI and misbehaving on the trading box.

Negative-tested in both conditions (no .env, and the 616-key throwaway): reverting the master load, reverting the Flattrade load, dropping the stub, and dropping the restore — all four caught, each by the guard meant for it; with the throwaway .env the reverted fixes also bring back the original failures. Control passes. The throwaway .env was removed every time.

Gates (this commit): master 620 OK, identical with and without a .env · 28 market-data-health · 1594 pytest · coverage report exits 0 under the 73% floor · coverage policy passed · ruff · mypy · compileall · bandit.

🤖 Generated with Claude Code

DoRmAmMu1997 and others added 2 commits September 23, 2026 23:19
…not later

One of the guards added in #181 fails on the operator's machine and nowhere
else. test_the_flag_is_set_for_the_load_only asserted that, by the time it ran,
os.environ held the same SL_HUNTING_ENABLED as before the master was loaded. On
the operator's box it found 'true' where it had been unset.

The loader is not the cause. It sets the flag inside patch.dict, which restores
the environment when the load ends - verified. The value comes back later, from
the NEXT import in the test module: flattrade_execution.py calls load_dotenv()
at import time, outside any patch, and re-reads Dependencies/.env. Proved in
isolation: importing that one module moves SL_HUNTING_ENABLED from unset to
'true'. CI and every worktree have no .env, which is why the guard passed there
and on #181's CI.

The fix records the value the moment the master's load block ends, before that
import, and the guard (renamed test_the_loader_puts_back_the_flag_it_set)
compares THAT against the pre-load value. It now tests exactly what it claims -
that this loader puts back what it found - and is indifferent to .env loading
elsewhere.

Reproduced without copying any credentials: a throwaway Dependencies/.env
containing only "SL_HUNTING_ENABLED=true" makes the old guard fail (617 run, 1
failure) and the new one pass; without it both pass. The throwaway file was
removed.

Negative-tested with AND without that .env: setting the flag for the whole
process before the load, and setting it after the load instead of inside
patch.dict, are both caught by the rewritten guard; a control rewording passes.

The flattrade .env leak itself is pre-existing and left alone here - it loads the
operator's whole .env into the test process, which makes the master suite less
hermetic on that machine than in CI. Flagged separately.

Gates: master 617 OK both with the one-line .env and without; 28
market-data-health; 1594 pytest; ruff; mypy (80 files); compileall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit on this PR made one guard read its value at the right
moment. This removes the cause it worked around: on the operator's machine the
master suite ran against the operator's .env, and CI - which has no .env - did
not.

TWO LEAKS, NOT ONE. The master, dhan_execution and flattrade_execution call
load_dotenv() on Dependencies/.env at import time.

  1. flattrade_execution and its diagnostic were imported OUTSIDE any patch,
     so their load copied the whole .env - flags and broker credentials alike
     - into os.environ for every test that followed.
  2. The master's own load was already inside patch.dict, which restored
     os.environ afterwards - but its load_dotenv still ran, and still let .env
     values decide the master's import-time CONSTANTS.

MEASURED, without touching a real .env: a throwaway Dependencies/.env holding
the 616 key NAMES from env.example, each set to a placeholder.

                               leaked into os.environ   tests that changed
  before (this PR's parent)            616                      21
  Flattrade imports wrapped only         0                      12
  all three loads isolated               0                       0

Of the 21, nine read the environment at run time (broker routing x4, the
live/paper gates, the per-strategy virtual-trading toggle) and the other twelve
depended on master constants set at import (the websocket feed, the strangle
re-entry, order correlation). None needs a .env - all pass without one - so
none was relying on it; they were exposed to it. With the operator's real
.env they passed only because those values happen to be sane.

THE FIX, TEST-SIDE ONLY. Every module-level repository load in the suite now
runs inside _isolated_from_dotenv: patch.dict restores os.environ exactly (and
carries the values a load needs - the master's DHAN_* stubs and
SL_HUNTING_ENABLED), and dotenv.load_dotenv is stubbed for the duration so the
file is not read at all. Production's import-time load_dotenv is unchanged, and
no production default moves. Follows the precedent in
Tests/Data Extractors/test_index_fetch_construction.py.

GUARDS, one of which holds in CI (where the others would be blind):
  * behavioural - snapshots os.environ before the first repository import and
    after the last, and fails naming any variable added, removed or changed
    (names only, never values: on that machine a value can be a credential);
  * probe - writes a throwaway module and .env to a temp dir and loads it with
    the REAL load_dotenv: bare, it must pick the value up (the control);
    isolated, it must not reach a constant or os.environ;
  * structural - the master and both Flattrade loads must go through the
    isolation, so a bare exec_module fails here instead of passing in CI and
    misbehaving on the trading machine.

Negative-tested in BOTH conditions (no .env, and the 616-key throwaway .env):
reverting the master load to a bare patch.dict, reverting the Flattrade load to
a bare exec_module, dropping the load_dotenv stub, and dropping the environment
restore - all four caught, each by the guard meant for it; with the throwaway
.env the reverted fixes also bring back the original test failures. A control
rewording passes. The throwaway .env was removed every time.

Gates: master 620 OK, identical with and without a .env; 28
market-data-health; 1594 pytest; coverage report exits 0 under fail_under 73.0;
check_coverage_thresholds.py passed; ruff; mypy (80 files); compileall; bandit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DoRmAmMu1997 DoRmAmMu1997 changed the title fix(tests): check the SL Hunting loader guard where the loader ends, not later fix(tests): keep Dependencies/.env out of the master suite (and fix the loader guard) Sep 24, 2026
@DoRmAmMu1997
DoRmAmMu1997 merged commit 9c7bd47 into main Sep 24, 2026
7 checks passed
@DoRmAmMu1997
DoRmAmMu1997 deleted the fix/sl-hunting-loader-guard branch September 24, 2026 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant