Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ T19|x|RELEASE-2 frontend CSV upload (new focused PR after T18): CSV upload UI→
T20|x|RELEASE-2 collaborator preview v1.6.0: prereq T18+T19+CI green on develop; optionally include #469 if node §B8-§B9 fixed → PR merge develop→main → `make version-minor` → verify V16 → oSPARC publish; goal: CSV upload+log-scale live for collaborator testing. SHIPPED: v1.6.0 tagged+released 2026-07-06 (`6c69488d`, stable #486); log-scale in (`58146f41`); repo since advanced to 1.6.4|V5,V9,V16,node/SPEC.md B8,B9
T21|x|REMOVE vendored `mmux_python` dep: inlined 6 used modules (`lhs`,`dakota_object`,`funs_create_dakota_conf`,`funs_data_processing`,`funs_evaluate`,`wiofiles`; dropped 3 unused: `dakota_object_map`,`funs_git`,`funs_plotting`) into `flaskapi/src/mmux_flaskapi/dakota/`; rewired blueprint imports + test mocks; removed `mmux-python` workspace dep + 6 dead transitive deps (gitpython,httpx,ipykernel,matplotlib,seaborn,tqdm) + `[tool.uv.workspace]`/`[tool.uv.sources]`/coverage-omit from `flaskapi/pyproject.toml`; stripped `setup-mmux-python`/`get-access-write-on-mmux-python` Makefile targets + `MMUX_PYTHON_TAG` pin + gitignore entries; `rm -rf flaskapi/mmux_python/`; eliminates the T1 drift class entirely (no more separate versioned dep to go stale)|V5,flaskapi/SPEC.md T15
T25|.|Document or script WSL2 Windows portproxy setup for local fallback app ports 8889-8892: discover WSL IP, delete stale rules, add rules, show rules, mention admin shell + firewall caveat|V20,V21
T26|.|backend test isolation: autouse pytest fixture allocates per-run `tmp_path` dirs for `LOCAL_STORE_DIR` + `TEXT_FILES_DIR` before `create_flask_app()`; reset persistence between tests; add regression run from dirty `runs_local` clone|V26,B25,flaskapi/SPEC.md V17
T26|x|backend test isolation: autouse pytest fixture allocates per-run `tmp_path` dirs for `LOCAL_STORE_DIR` + `TEXT_FILES_DIR` before `create_flask_app()`; reset persistence between tests; add regression run from dirty `runs_local` clone. DONE: `conftest.py::isolate_persistence_dirs` (autouse, patches `ljs.LOCAL_STORE_{DIR,FILE}` + `textfile.FILES_STORAGE_DIR` globals + env; fresh dir/test = reset by construction) + `tests/test_test_isolation.py` (B15 27-collection repro, repo store byte-stability, real text-file write → tmp). ORACLE: full suite **600 passed with the 13:54 dirty `runs_local` store present** (same file that failed 9 tests this morning, no env workaround)|V26,B25,flaskapi/SPEC.md V17
T27|x|Node deprecation cleanup: upgrade ESLint/toolchain off deprecated ESLint 8 dependency chain (`inflight`, `@humanwhocodes/*` [i.e. eslint-8's `config-array`/`object-schema`], `rimraf@3`, old nested `glob`); clean `npm ci` emits no listed deprecation warnings. DONE: eslint v9.39.5, lock grep `inflight`=0/`rimraf`=0/config-array absent; `npm ci --dry-run` → 0 deprecation warnings (fresh `rm -rf node_modules && npm ci` clean 2026-10-08). NB: `@humanwhocodes/{module-importer,retry}` remain — current ESLint-9 deps, ⊥ deprecated chain|—
T28|.|incrementally fix the 30 `react-hooks/set-state-in-effect` warnings surfaced by eslint-plugin-react-hooks v7 (rule downgraded error→warn in T27 to unblock the ESLint v9 upgrade w/o a risky bulk behavioral refactor); mostly persistence/context-hydration + job-status polling patterns, needs case-by-case triage (derive-during-render vs justified external-sync exception)|node/eslint.config.js
T29|x|Correlation and Sobol sensitivity-analysis backend endpoints, SciPy computation, response-key preservation, and numerical regression tests|flaskapi/SPEC.md T25
Expand Down Expand Up @@ -175,3 +175,4 @@ B23gu|2026-09-23|backend prod entrypoint used `uvx gunicorn`, bypassing declared
B26qf|2026-09-23|PR #622 CI `npm ci` rejected the committed coverage dependency addition as out of sync, so both `node-tests` and `prek` failed before their actual checks; a clean install against the pushed lockfile passed locally|V44qf
B24kp|2026-09-29|PR #648 Copilot review: V31vr's `user: "${UID:-1000}:${GID:-1000}"` never actually pinned the host identity — `UID`/`GID` are unexported bash-internal variables (and make runs recipes via `/bin/sh`, which doesn't set them either), so every `docker compose` launch fell back to `1000:1000` on non-1000 hosts while the substring regression test kept passing on the placeholder text; launchers must pass explicit `HOST_UID/HOST_GID` and tests must enforce both sides of the interpolation|V31vr
B25|2026-10-01|#664 shipped the bounds-editor header, yet the committed Sobol baseline LACKED it and the e2e verify passed twice locally + CI: header band + ~39px plot shift fell just under `maxDiffPixelRatio` 0.01 on the 1920×1080 viewport — a band meant to absorb font/AA drift silently absorbed a REAL UI change (surfaced only when #665 regenerated baselines in the pinned image)|strict 0.0 tolerance (root V10 re-worded): ⊥ band can re-absorb a semantic diff; the pinned image (V12) owns determinism, so the contract is ZERO comparator-COUNTED differing pixels (per V10: 0.2 color floor + AA classification deliberately ⊥ byte-identity) — if the image changes, baselines are regenerated IN it|V10,V12,https://github.com/ITISFoundation/mmux_vite/pull/665
B27rt|2026-10-08|GH-Copilot #715 review: the §V26 isolation guard's text-file test ALSO asserted `/text-files/v26.txt` never exists — a READ of SHARED container state, i.e. the very class of pre-existing external state V26/B15 isolation exists to tolerate; a dev stack that had landed that file would fail an otherwise-green suite (hermeticity broken in the GUARD itself)|dropped the shared-path existence assert: the successful read from the patched tmp dir is the hermetic proof the request used the isolated destination (⊥ reading the unpatched path adds external state, not coverage); docstring names the rule|V26,T26,B15,https://github.com/ITISFoundation/mmux_vite/pull/715
19 changes: 18 additions & 1 deletion flaskapi/src/mmux_flaskapi/blueprints/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,31 @@
deployment_bp = Blueprint("deployment", __name__)


# SPEC.md §V4 (fork issue #80): these three env vars are enums, not free text.
# Presence-only checking let `SERVICE_MODE=FOO` pass with 200, so a typo'd
# service definition or a misconfigured compose env silently produced an app
# that served a mode no view implements. Out-of-range → the same JSON error
# path as "not set" (KeyError → api_endpoint surface → 500), in every caller
# of these getters (deployment endpoints AND sampling's DEPLOYMENT_MODE read).
_VALID_ENV_VALUES: dict[str, frozenset[str]] = {
"SERVICE_MODE": frozenset({"UQ", "SUMO", "MOGA"}),
"PERMISSIONS": frozenset({"READ-ONLY", "WRITE"}),
"DEPLOYMENT_MODE": frozenset({"LOCAL", "OSPARC"}),
}


def _get_required_env_var(name: str) -> str:
try:
value = os.environ[name]
_logger.info("%s: %s", name, value)
return value
except KeyError as exc:
_logger.error("%s environment variable is not set.", name)
raise KeyError(f"{name} not set") from exc
allowed = _VALID_ENV_VALUES.get(name)
if allowed is not None and value not in allowed:
_logger.error("%s: %r is not one of %s", name, value, sorted(allowed))
raise KeyError(f"{name} '{value}' is not one of {sorted(allowed)}")
return value


def get_service_mode_value() -> str:
Expand Down
30 changes: 30 additions & 0 deletions flaskapi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,36 @@ def mock_test_env_vars():
yield


@pytest.fixture(autouse=True)
def isolate_persistence_dirs(tmp_path, monkeypatch):
"""Root SPEC.md §V26 / §T26 (B15): every test gets per-run temp dirs for the
local job store and the text-file storage, before any app or persistence
code can read them.

B15 bit again on 2026-10-08: a `make run-*` dev stack left 3 collections in
the repository `flaskapi/runs_local/` and 9 list-endpoint tests failed
(`assert 3 == 0`) until the store was manually isolated. The blueprints
read these module globals at request time, so patching the attributes is
what the request path actually sees; the env vars are patched too for any
code that re-reads them (e.g. a re-import). Fresh dir per test ⇒ persistence
is reset between tests by construction.
"""
from mmux_flaskapi.blueprints import textfile
from mmux_flaskapi.utils import local_job_store as ljs

store_dir = tmp_path / "runs_local"
files_dir = tmp_path / "text_files"
files_dir.mkdir()
monkeypatch.setattr(ljs, "LOCAL_STORE_DIR", store_dir)
monkeypatch.setattr(ljs, "LOCAL_STORE_FILE", store_dir / "uploaded_job_collections_store.json")
monkeypatch.setattr(textfile, "FILES_STORAGE_DIR", files_dir)
with patch.dict(
"os.environ",
{"LOCAL_STORE_DIR": str(store_dir), "TEXT_FILES_DIR": str(files_dir)},
):
yield


@pytest.fixture(autouse=True)
def default_osparc_reachable():
"""Default the oSPARC connectivity probe (`OsparcApi.is_connected()`) to "reachable"
Expand Down
5 changes: 5 additions & 0 deletions flaskapi/tests/test_api_error_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

from typing import Any

import pytest

# §V32qt (fork issue #82): every flaskapi test case carries ≥1 tier marker.
pytestmark = pytest.mark.unit


def assert_error_json(response: Any, status: int) -> dict[str, Any]:
assert response.status_code == status, response.get_data(as_text=True)[:200]
Expand Down
Loading
Loading