Add workflow validation and self-service operations

This commit is contained in:
saymrwulf 2026-04-15 11:52:47 +02:00
parent 773ec2ba21
commit 9fe3ed6b22
12 changed files with 702 additions and 43 deletions

View file

@ -4,42 +4,55 @@ Use this the first time you open the platform, or anytime you want a clean local
## Checklist
1. Start the repo-local server:
1. If this is a fresh Mac or a fresh clone, bootstrap the repo first:
```bash
bash ./scripts/bootstrap_mac.sh
```
2. Check the local state:
```bash
bash ./scripts/project_status.sh
bash ./scripts/run_validation.sh --quick
```
3. Start the repo-local server:
```bash
./scripts/start_jupyter.sh
```
2. Open the localhost URL printed by the script.
3. Confirm the kernel in the top-right corner is `QuantumLearning (.venv)`.
4. Run `Kernel -> Restart Kernel and Run All Cells...` inside `notebooks/COURSE_BLUEPRINT.ipynb`.
5. Read the rebuilt `Foundations` band in this order:
4. Open the localhost URL printed by the script.
5. Confirm the kernel in the top-right corner is `QuantumLearning (.venv)`.
6. Run `Kernel -> Restart Kernel and Run All Cells...` inside `notebooks/COURSE_BLUEPRINT.ipynb`.
7. Read the rebuilt `Foundations` band in this order:
`module_01_principles_and_circuit_literacy`
`module_02_qubit_and_statevector_intuition`
`module_03_gates_and_measurement`
and inside each module use:
`lecture.ipynb -> lab.ipynb -> problems.ipynb -> studio.ipynb`.
6. Continue into the rebuilt `Qiskit Engineering` band:
8. Continue into the rebuilt `Qiskit Engineering` band:
`module_01_circuit_construction_and_analysis`
`module_02_transpilation_and_visualization`
`module_03_simulation_and_noise_models`
again using:
`lecture.ipynb -> lab.ipynb -> problems.ipynb -> studio.ipynb`.
7. Continue into the rebuilt `Algorithmic Design` band:
9. Continue into the rebuilt `Algorithmic Design` band:
`module_01_deutsch_family`
`module_02_bernstein_vazirani`
`module_03_qft`
`module_04_grover`
again using:
`lecture.ipynb -> lab.ipynb -> problems.ipynb -> studio.ipynb`.
8. Continue into the rebuilt `Professional Design` band:
10. Continue into the rebuilt `Professional Design` band:
`module_01_qiskit_patterns`
`module_02_hardware_aware_redesign`
`module_03_noise_aware_verification`
`module_04_capstone_design_review`
again using:
`lecture.ipynb -> lab.ipynb -> problems.ipynb -> studio.ipynb`.
9. Use `notebooks/PROFESSIONAL_PATH.ipynb` when you want the full mastery-model context.
11. Use `notebooks/PROFESSIONAL_PATH.ipynb` when you want the full mastery-model context.
## Expected Local Behavior
@ -54,6 +67,7 @@ Use this the first time you open the platform, or anytime you want a clean local
If you want a quick terminal check before opening the browser:
```bash
source .venv/bin/activate
pytest -q
bash ./scripts/run_validation.sh --quick
```
For the full operational guide, see [OPERATIONS.md](/Users/oho/GitClone/CodexProjects/QuantumLearning/OPERATIONS.md).

175
OPERATIONS.md Normal file
View file

@ -0,0 +1,175 @@
# Operations
This file is the self-service operations manual for `QuantumLearning`. It is written to match the repo as it actually exists now, not a hypothetical future state.
## Operating Model
The project is intentionally local-first and repo-local:
- Python environment lives in `.venv/`
- Jupyter config and runtime live in `.jupyter_config/`, `.jupyter_data/`, `.jupyter_runtime/`, `.ipython/`, and `.cache/matplotlib/`
- Playwright browser runtime lives in `.playwright-browsers/`
- validation artifacts live in `.tmp_test_artifacts/`
That means the day-to-day operational commands are all inside this repo.
## Bootstrap On A New Mac
Prerequisites:
1. macOS
2. Python `3.12`
3. internet access for the initial dependency and browser download
If `python3.12` is missing, install it first.
Typical options:
```bash
brew install python@3.12
```
or install Python `3.12` from `python.org`.
Then bootstrap the repo:
```bash
cd /path/to/QuantumLearning
bash ./scripts/bootstrap_mac.sh
```
That script will:
- create `.venv/` if needed
- install the Python dependencies from `pyproject.toml`
- install the project-local Playwright Chromium runtime into `.playwright-browsers/`
If you want the bootstrap to finish with a full validation run:
```bash
bash ./scripts/bootstrap_mac.sh --run-validation
```
If you want to delay the browser download:
```bash
bash ./scripts/bootstrap_mac.sh --skip-browser
```
## Start And Use The Course
Start JupyterLab:
```bash
./scripts/start_jupyter.sh
```
Open the exact localhost URL printed by the script. Do not guess the port; Jupyter will automatically move to the next free port if the requested one is already occupied.
Use the course in this order:
1. `notebooks/COURSE_BLUEPRINT.ipynb`
2. each module as `lecture -> lab -> problems -> studio`
## Monitoring The Project
Use the repo-native status script:
```bash
bash ./scripts/project_status.sh
```
It reports:
- current git branch and commit
- whether the worktree is clean or dirty
- whether `.venv/` exists and which Python version it uses
- whether the project-local Playwright browser runtime is installed
- whether a repo-local Jupyter server is running and, if so, its full URL
If you lose the Jupyter URL, `bash ./scripts/project_status.sh` is the fastest recovery path.
In restricted automation sandboxes, the script may report that the live process probe is unavailable. In a normal terminal on your Mac, it will report the running server directly when the runtime file and process are both visible.
## Validation Modes
Quick local validation:
```bash
bash ./scripts/run_validation.sh --quick
```
This runs:
- `ruff check src tests scripts`
- `pytest -m "not browser and not notebook"`
Standard validation:
```bash
bash ./scripts/run_validation.sh --standard
```
This runs the full pytest suite without coverage reporting.
Release-style validation:
```bash
bash ./scripts/run_validation.sh --full
```
This runs:
- `ruff check src tests scripts`
- full pytest suite
- coverage reporting
In restricted automation sandboxes, the browser and notebook execution suites may skip because localhost kernel and browser ports are blocked. In a normal terminal on your Mac, the same command should run those suites instead of skipping them.
At the current state of the repo, the full validation suite includes:
- curriculum/config/runtime tests
- notebook pedagogy tests
- notebook execution tests
- real JupyterLab browser workflow validation
- assessment and capstone validation tests
## What The Browser Workflow Validation Now Covers
The browser suite no longer stops at “page opened.”
It now validates learner-style flows such as:
- opening notebooks through JupyterLab
- running all notebook cells
- answering a multiple-choice quiz
- editing an `editable_circuit_lab` code cell and re-rendering it
- completing an evidence checklist
- filling a feedback iteration panel
- moving rubric sliders and observing the grading summary update
## Recovery Checklist
If something feels wrong, use this order:
1. `bash ./scripts/project_status.sh`
2. `bash ./scripts/run_validation.sh --quick`
3. if needed, `bash ./scripts/run_validation.sh --full`
4. restart Jupyter with `./scripts/start_jupyter.sh`
If `.venv/` is missing or broken, re-run:
```bash
bash ./scripts/bootstrap_mac.sh
```
## Another-Mac Transfer Pattern
The clean transfer model is:
1. clone or copy the repo to the new Mac
2. make sure Python `3.12` exists
3. run `bash ./scripts/bootstrap_mac.sh`
4. run `bash ./scripts/run_validation.sh --quick`
5. run `./scripts/start_jupyter.sh`
That is the intended self-service installation path. You do not need agent support for normal setup, validation, status checks, or Jupyter startup.

View file

@ -31,11 +31,13 @@ The current rebuild direction is now explicit:
```text
QuantumLearning/
├── scripts/
├── assets/figures/
├── configs/
├── notebooks/
├── src/
├── tests/
├── .playwright-browsers/
├── .venv/
├── pyproject.toml
└── README.md
@ -43,14 +45,20 @@ QuantumLearning/
## Quick Start
Use the repo-local Python 3.12 environment.
Use the repo-local bootstrap script on macOS:
```bash
cd /Users/oho/GitClone/CodexProjects/QuantumLearning
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
bash ./scripts/bootstrap_mac.sh
```
That script creates `.venv/`, installs the Python dependencies, and installs the project-local Playwright Chromium runtime into `.playwright-browsers/`.
If you want a fast operational check immediately after bootstrap:
```bash
bash ./scripts/project_status.sh
bash ./scripts/run_validation.sh --quick
```
Launch Jupyter from the project root with the repo-local wrapper so the notebooks, kernel, and local config stay aligned.
@ -70,18 +78,18 @@ If you want a different local port:
Run the tests:
```bash
source .venv/bin/activate
pytest
bash ./scripts/run_validation.sh --standard
```
Run the measured local coverage report:
```bash
source .venv/bin/activate
pytest --cov=quantum_learning --cov-report=term-missing
bash ./scripts/run_validation.sh --full
```
Install the project-local browser runtime used by the real JupyterLab UX tests:
On a normal Mac terminal, the full validation path runs the browser and notebook execution suites as well. In restricted automation sandboxes, those suites may skip because localhost kernel or browser ports are blocked.
If you deliberately skipped the local browser runtime during bootstrap, install it later with:
```bash
source .venv/bin/activate
@ -132,6 +140,7 @@ The `Professional Design` band is now rebuilt as well:
The older single-notebook sequence is still present as legacy reference material, but the canonical course path is now the full bundle architecture across all four technical bands.
See [FIRST_RUN.md](/Users/oho/GitClone/CodexProjects/QuantumLearning/FIRST_RUN.md) for the short startup checklist.
See [OPERATIONS.md](/Users/oho/GitClone/CodexProjects/QuantumLearning/OPERATIONS.md) for setup on another Mac, monitoring, validation, and recovery.
## Local-First Design Rules
@ -139,3 +148,4 @@ See [FIRST_RUN.md](/Users/oho/GitClone/CodexProjects/QuantumLearning/FIRST_RUN.m
- No IBM token is required.
- Transpilation examples use local constraints and simulator-compatible backends.
- Circuit graphics are first-class and rely on local `matplotlib`, `pylatexenc`, and `Pillow`.
- The browser validation stack uses a repo-local Playwright runtime in `.playwright-browsers/`.

96
scripts/bootstrap_mac.sh Executable file
View file

@ -0,0 +1,96 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
INSTALL_BROWSER=1
RUN_VALIDATION=0
usage() {
cat <<'EOF'
Usage: bash ./scripts/bootstrap_mac.sh [--skip-browser] [--run-validation]
Bootstrap QuantumLearning on a Mac using only repo-local runtime paths.
Options:
--skip-browser Skip installing the project-local Playwright Chromium runtime.
--run-validation Run the full validation suite after bootstrapping.
-h, --help Show this help.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-browser)
INSTALL_BROWSER=0
shift
;;
--run-validation)
RUN_VALIDATION=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
esac
done
resolve_python() {
if command -v python3.12 >/dev/null 2>&1; then
echo "python3.12"
return 0
fi
if command -v python3 >/dev/null 2>&1; then
if python3 -c 'import sys; raise SystemExit(0 if sys.version_info[:2] == (3, 12) else 1)'; then
echo "python3"
return 0
fi
fi
return 1
}
if ! PYTHON_BIN="$(resolve_python)"; then
cat <<'EOF' >&2
Python 3.12 is required.
Install it first, then re-run this script.
Typical options on macOS:
brew install python@3.12
or install Python 3.12 from python.org
EOF
exit 1
fi
cd "$ROOT_DIR"
if [[ ! -d "$ROOT_DIR/.venv" ]]; then
"$PYTHON_BIN" -m venv "$ROOT_DIR/.venv"
fi
source "$ROOT_DIR/.venv/bin/activate"
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
if [[ "$INSTALL_BROWSER" -eq 1 ]]; then
PLAYWRIGHT_BROWSERS_PATH="$ROOT_DIR/.playwright-browsers" python -m playwright install chromium
fi
if [[ "$RUN_VALIDATION" -eq 1 ]]; then
bash "$ROOT_DIR/scripts/run_validation.sh"
fi
cat <<EOF
Bootstrap complete.
Next steps:
bash ./scripts/project_status.sh
bash ./scripts/run_validation.sh --quick
./scripts/start_jupyter.sh
EOF

0
scripts/normalize_notebook_ids.py Normal file → Executable file
View file

81
scripts/project_status.sh Executable file
View file

@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
usage() {
cat <<'EOF'
Usage: bash ./scripts/project_status.sh
Print the current local operational state of the QuantumLearning repo:
git state, repo-local Python environment, Playwright browser runtime, and
the latest repo-local Jupyter server if one is running.
EOF
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
cd "$ROOT_DIR"
BRANCH="$(git branch --show-current)"
COMMIT="$(git rev-parse --short HEAD)"
if [[ -n "$(git status --short)" ]]; then
GIT_STATE="dirty"
else
GIT_STATE="clean"
fi
echo "Root: $ROOT_DIR"
echo "Git: $BRANCH @ $COMMIT ($GIT_STATE)"
if [[ -x "$ROOT_DIR/.venv/bin/python" ]]; then
PYTHON_VERSION="$("$ROOT_DIR/.venv/bin/python" -c 'import sys; print(sys.version.split()[0])')"
echo "Venv: present (Python $PYTHON_VERSION)"
else
echo "Venv: missing"
fi
if compgen -G "$ROOT_DIR/.playwright-browsers/chromium-*" >/dev/null; then
echo "Browser runtime: installed"
else
echo "Browser runtime: missing"
fi
if compgen -G "$ROOT_DIR/.jupyter_runtime/jpserver-*.json" >/dev/null; then
LATEST_SERVER_JSON="$(ls -t "$ROOT_DIR"/.jupyter_runtime/jpserver-*.json | head -n 1)"
"$ROOT_DIR/.venv/bin/python" - <<'PY' "$LATEST_SERVER_JSON"
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
path = Path(sys.argv[1])
data = json.loads(path.read_text())
pid = int(data["pid"])
try:
os.kill(pid, 0)
except ProcessLookupError:
print("Jupyter: runtime file exists but process is not running")
print(f" Runtime file: {path}")
except PermissionError:
print("Jupyter: runtime file present; live process probe is unavailable in this environment")
print(f" URL: {data['url']}lab/tree/notebooks/COURSE_BLUEPRINT.ipynb?token={data['token']}")
print(f" Runtime file: {path}")
else:
print(f"Jupyter: running (pid {pid})")
print(f" URL: {data['url']}lab/tree/notebooks/COURSE_BLUEPRINT.ipynb?token={data['token']}")
print(f" Runtime file: {path}")
PY
else
echo "Jupyter: no repo-local runtime file found"
fi
echo "Validation:"
echo " Quick: bash ./scripts/run_validation.sh --quick"
echo " Full: bash ./scripts/run_validation.sh --full"

66
scripts/run_validation.sh Executable file
View file

@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
MODE="full"
usage() {
cat <<'EOF'
Usage: bash ./scripts/run_validation.sh [--quick|--standard|--full]
Validation modes:
--quick Ruff + pytest without browser/notebook execution suites.
--standard Ruff + full pytest suite.
--full Ruff + full pytest suite with coverage report. Default.
-h, --help Show this help.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--quick)
MODE="quick"
shift
;;
--standard)
MODE="standard"
shift
;;
--full)
MODE="full"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
esac
done
if [[ ! -x "$ROOT_DIR/.venv/bin/python" ]]; then
echo "Missing repo-local .venv. Run bash ./scripts/bootstrap_mac.sh first." >&2
exit 1
fi
cd "$ROOT_DIR"
source "$ROOT_DIR/.venv/bin/activate"
export PLAYWRIGHT_BROWSERS_PATH="$ROOT_DIR/.playwright-browsers"
ruff check src tests scripts
case "$MODE" in
quick)
python -m pytest -q -m "not browser and not notebook"
;;
standard)
python -m pytest -q
;;
full)
python -m pytest --cov=quantum_learning --cov-report=term-missing -q
;;
esac

View file

@ -87,3 +87,22 @@ def test_capstone_rubric_maps_to_intended_competence_dimensions():
"recommendation",
"risk",
]
def test_capstone_rubric_rewards_complete_design_review_evidence():
result = score_named_rubric(
"capstone_design_review",
{
"brief": 4,
"candidates": 4,
"ideal": 4,
"hardware": 4,
"noise": 4,
"recommendation": 4,
"risk": 4,
},
)
assert result.total_score == 28
assert result.passed
assert result.band == "mastery-ready"

View file

@ -53,6 +53,62 @@ def _wait_for_notebook(browser_page, notebook_filename: str, heading: str) -> No
)
def _open_notebook_from_directory(
browser_page,
jupyter_lab_server,
directory: str,
notebook_filename: str,
heading: str,
) -> None:
url = f"{jupyter_lab_server['base_url']}/lab/tree/{directory}?token={jupyter_lab_server['token']}"
browser_page.goto(url, wait_until="networkidle")
browser_page.locator(".jp-DirListing-itemText", has_text=notebook_filename).first.wait_for(
timeout=60000
)
browser_page.locator(".jp-DirListing-itemText", has_text=notebook_filename).first.dblclick()
_wait_for_notebook(browser_page, notebook_filename, heading)
def _run_all_cells(browser_page) -> None:
browser_page.locator("text=Run").first.click()
browser_page.locator("text=Run All Cells").first.click()
try:
browser_page.get_by_role("button", name="Select").click(timeout=5000)
except PlaywrightTimeoutError:
pass
def _find_code_editor(browser_page):
textareas = browser_page.locator("textarea")
for index in range(textareas.count()):
candidate = textareas.nth(index)
if not candidate.is_visible():
continue
try:
value = candidate.input_value()
except PlaywrightTimeoutError: # pragma: no cover - widget race
continue
if "QuantumCircuit" in value:
return candidate
raise AssertionError("Could not locate an editable circuit code editor.")
def _wait_for_text_in_body(browser_page, expected_text: str, *, timeout_seconds: int = 120) -> None:
deadline = time.time() + timeout_seconds
last_body = ""
while time.time() < deadline:
last_body = browser_page.locator("body").inner_text()
if expected_text in last_body:
return
time.sleep(1)
raise AssertionError(
f"Did not find {expected_text!r} in rendered page text.\nLast body text excerpt:\n"
f"{last_body[-4000:]}"
)
@pytest.fixture(scope="module")
def jupyter_lab_server():
if sync_playwright is None:
@ -141,31 +197,68 @@ def test_course_blueprint_loads_in_jupyterlab(browser_page, jupyter_lab_server):
@pytest.mark.browser
def test_capstone_studio_runs_and_renders_controls(browser_page, jupyter_lab_server):
url = (
f"{jupyter_lab_server['base_url']}/lab/tree/notebooks/professional/"
"module_04_capstone_design_review"
f"?token={jupyter_lab_server['token']}"
)
browser_page.goto(url, wait_until="networkidle")
browser_page.locator(".jp-DirListing-itemText", has_text="studio.ipynb").first.wait_for(
timeout=60000
)
browser_page.locator(".jp-DirListing-itemText", has_text="studio.ipynb").first.dblclick()
_wait_for_notebook(
def test_foundations_lab_supports_quiz_answering_and_circuit_editing(
browser_page,
jupyter_lab_server,
):
_open_notebook_from_directory(
browser_page,
jupyter_lab_server,
"notebooks/foundations/module_01_principles_and_circuit_literacy",
"lab.ipynb",
"Principles and Circuit Literacy Lab",
)
_run_all_cells(browser_page)
browser_page.get_by_label("A. Only one deterministic outcome from the all-zero input").check()
browser_page.get_by_role("button", name="Check answer").first.click()
browser_page.locator("text=Correct.").first.wait_for(timeout=120000)
editor = _find_code_editor(browser_page)
editor.fill(
'circuit = QuantumCircuit(1, 1, name="workflow")\n'
"circuit.x(0)\n"
"circuit.measure(0, 0)\n"
)
browser_page.get_by_role("button", name="Render circuit").first.click()
browser_page.locator("pre", has_text="{'1': 256}").first.wait_for(timeout=120000)
@pytest.mark.browser
def test_capstone_studio_supports_checklist_feedback_and_rubric_workflows(
browser_page,
jupyter_lab_server,
):
_open_notebook_from_directory(
browser_page,
jupyter_lab_server,
"notebooks/professional/module_04_capstone_design_review",
"studio.ipynb",
"Capstone Circuit Design Review Studio",
)
browser_page.locator("text=Run").first.click()
browser_page.locator("text=Run All Cells").first.click()
try:
browser_page.get_by_role("button", name="Select").click(timeout=5000)
except PlaywrightTimeoutError:
pass
_run_all_cells(browser_page)
browser_page.get_by_role("button", name="Render circuit").first.wait_for(timeout=120000)
browser_page.get_by_role("button", name="Check answer").first.wait_for(timeout=120000)
browser_page.locator("text=Evidence Checklist").first.wait_for(timeout=120000)
browser_page.locator("text=Capstone Circuit Design Review Evidence Checklist").first.wait_for(
timeout=120000
)
browser_page.get_by_placeholder("State the current claim or judgement.").fill(
"The middle-root GHZ candidate best satisfies the declared local brief."
)
browser_page.get_by_placeholder("Name the exact evidence supporting that claim.").fill(
"It preserves the ideal target, compiles with lower burden on the line, and retains stronger noisy support."
)
browser_page.get_by_placeholder("Name the main remaining risk, assumption, or weakness.").fill(
"The comparison still depends on a synthetic local noise model."
)
browser_page.get_by_placeholder("State the next revision, experiment, or rewrite.").fill(
"Compare the same candidates under a second line-constrained noise profile."
)
browser_page.get_by_role("button", name="Review draft").click()
browser_page.locator("text=Missing sections: None").first.wait_for(timeout=120000)
_wait_for_text_in_body(browser_page, "Capstone Circuit Design Review Studio Self-Grading")
_wait_for_text_in_body(browser_page, "Brief Clarity")
_wait_for_text_in_body(browser_page, "Total: 0/28")
_wait_for_text_in_body(browser_page, "Band: revision-needed")

View file

@ -1,3 +1,5 @@
import ipywidgets as widgets
from quantum_learning import load_assessment_blueprint
from qiskit import QuantumCircuit
@ -76,6 +78,23 @@ def test_rubric_scorecard_builds_widget():
assert widget.children
def test_rubric_scorecard_updates_summary_when_sliders_change():
rubric = load_assessment_blueprint().get_rubric("capstone_design_review")
widget = rubric_scorecard(rubric, title="Capstone Review")
sliders = [child for child in widget.children if isinstance(child, widgets.IntSlider)]
summary = widget.children[-1]
assert len(sliders) == 7
assert "Total:</b> 0/28" in summary.value
for slider in sliders:
slider.value = slider.max
assert "Total:</b> 28/28" in summary.value
assert "Band:</b> mastery-ready" in summary.value
def test_feedback_iteration_panel_builds_widget():
widget = feedback_iteration_panel(
title="Revision Loop",
@ -94,3 +113,26 @@ def test_evidence_checklist_builds_widget():
title="Checklist",
)
assert widget.children
def test_evidence_checklist_updates_summary_when_boxes_change():
widget = evidence_checklist(
[
"Objective stated",
"Evidence cited",
"Risk named",
],
title="Checklist",
)
checkboxes = [child for child in widget.children if isinstance(child, widgets.Checkbox)]
summary = widget.children[-1]
assert len(checkboxes) == 3
assert "Completion:</b> 0%" in summary.value
for checkbox in checkboxes:
checkbox.value = True
assert "Completion:</b> 100%" in summary.value
assert "Ready for review:</b> Yes" in summary.value

59
tests/test_operations.py Normal file
View file

@ -0,0 +1,59 @@
from __future__ import annotations
import subprocess
from quantum_learning.config import project_root
def test_bootstrap_script_help_works():
result = subprocess.run(
["bash", "scripts/bootstrap_mac.sh", "--help"],
cwd=project_root(),
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0
assert "Usage:" in result.stdout
def test_validation_script_help_works():
result = subprocess.run(
["bash", "scripts/run_validation.sh", "--help"],
cwd=project_root(),
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0
assert "--full" in result.stdout
def test_project_status_script_runs():
result = subprocess.run(
["bash", "scripts/project_status.sh"],
cwd=project_root(),
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0
assert "Git:" in result.stdout
assert "Validation:" in result.stdout
def test_operations_docs_use_tested_command_forms():
root = project_root()
expected_commands = [
"bash ./scripts/bootstrap_mac.sh",
"bash ./scripts/project_status.sh",
"bash ./scripts/run_validation.sh --quick",
]
for relative_path in ("README.md", "FIRST_RUN.md", "OPERATIONS.md"):
text = (root / relative_path).read_text()
for command in expected_commands:
assert command in text

View file

@ -79,6 +79,10 @@ def test_first_run_checklist_exists():
assert (project_root() / "FIRST_RUN.md").exists()
def test_operations_guide_exists():
assert (project_root() / "OPERATIONS.md").exists()
def test_repo_local_jupyter_config_exists():
assert (project_root() / ".jupyter_config" / "jupyter_lab_config.py").exists()
assert (project_root() / ".jupyter_config" / "jupyter_server_config.py").exists()