Skip to content

Commit 6837667

Browse files
committed
chore: generated test and production code for user-story-for-python-dotnet-for-env-check.md
1 parent 3ace86f commit 6837667

6 files changed

Lines changed: 634 additions & 0 deletions

File tree

dialogs/dialog-2026-03-07--16-30.md

Lines changed: 404 additions & 0 deletions
Large diffs are not rendered by default.

env-check/env_check.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import os
2+
import sys
3+
from dotenv import load_dotenv
4+
5+
6+
VAR_SPECS = [
7+
{"name": "DB_HOST", "type": "str"},
8+
{"name": "DB_PORT", "type": "int", "min": 1, "max": 65535},
9+
{"name": "DB_NAME", "type": "str"},
10+
{"name": "DB_USER", "type": "str"},
11+
{"name": "DB_PASSWORD", "type": "str"},
12+
{"name": "BATCH_SIZE", "type": "int", "min": 1, "range_desc": "BATCH_SIZE > 0"},
13+
{"name": "MAX_RETRIES", "type": "int", "min": 0, "range_desc": "MAX_RETRIES >= 0"},
14+
{"name": "DRY_RUN", "type": "bool"},
15+
]
16+
17+
VALID_BOOLS = frozenset({"true", "false", "1", "0"})
18+
19+
20+
def _check_int(name: str, value: str, spec: dict) -> str | None:
21+
try:
22+
int_val = int(value)
23+
except ValueError:
24+
return f'[FAIL] {name}: 无法转换为整数(当前值:"{value}")'
25+
26+
lo, hi = spec.get("min"), spec.get("max")
27+
if hi is not None:
28+
if not (lo <= int_val <= hi):
29+
return f"[FAIL] {name}: 值超出范围(需满足 {lo} <= {name} <= {hi},当前值:{int_val})"
30+
elif lo is not None and int_val < lo:
31+
range_desc = spec.get("range_desc", f"{name} >= {lo}")
32+
return f"[FAIL] {name}: 值超出范围(需满足 {range_desc})"
33+
return None
34+
35+
36+
def collect_errors(env_vars: dict) -> list[str]:
37+
errors = []
38+
for spec in VAR_SPECS:
39+
name = spec["name"]
40+
value = env_vars.get(name)
41+
42+
if value is None:
43+
errors.append(f"[FAIL] {name}: 未设置")
44+
continue
45+
46+
if spec["type"] == "str":
47+
if value == "":
48+
errors.append(f"[FAIL] {name}: 值为空字符串")
49+
elif spec["type"] == "int":
50+
err = _check_int(name, value, spec)
51+
if err:
52+
errors.append(err)
53+
else: # bool
54+
if value.lower() not in VALID_BOOLS:
55+
errors.append(f'[FAIL] {name}: 非法布尔值(当前值:"{value}",合法值为 true/false/1/0)')
56+
57+
return errors
58+
59+
60+
def validate_env(dotenv_path=None) -> list[str]:
61+
load_dotenv(dotenv_path=dotenv_path, override=True)
62+
return collect_errors(os.environ)
63+
64+
65+
def main():
66+
errors = validate_env()
67+
if errors:
68+
for msg in errors:
69+
print(msg)
70+
sys.exit(1)
71+
else:
72+
print("[OK] 所有环境变量校验通过")
73+
sys.exit(0)
74+
75+
76+
if __name__ == "__main__":
77+
main()

env-check/pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
testpaths = test

env-check/test/__init__.py

Whitespace-only changes.

env-check/test/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
import sys
3+
from pathlib import Path
4+
import pytest
5+
6+
sys.path.insert(0, str(Path(__file__).parent.parent))
7+
8+
REQUIRED_VAR_NAMES = [
9+
"DB_HOST", "DB_PORT", "DB_NAME", "DB_USER", "DB_PASSWORD",
10+
"BATCH_SIZE", "MAX_RETRIES", "DRY_RUN",
11+
]
12+
13+
@pytest.fixture
14+
def clean_env():
15+
"""Remove required env vars before test; restore original state after."""
16+
saved = {name: os.environ.pop(name, None) for name in REQUIRED_VAR_NAMES}
17+
yield
18+
for name, original_value in saved.items():
19+
if original_value is None:
20+
os.environ.pop(name, None)
21+
else:
22+
os.environ[name] = original_value

env-check/test/test_env_check.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import pytest
2+
from pathlib import Path
3+
from env_check import validate_env
4+
5+
VALID_ENV_CONTENT = (
6+
"DB_HOST=localhost\n"
7+
"DB_PORT=5432\n"
8+
"DB_NAME=analytics\n"
9+
"DB_USER=admin\n"
10+
"DB_PASSWORD=s3cret\n"
11+
"BATCH_SIZE=500\n"
12+
"MAX_RETRIES=3\n"
13+
"DRY_RUN=false\n"
14+
)
15+
16+
17+
# AC-H1: All vars valid → no errors, exit 0
18+
def test_h1_all_valid(clean_env, tmp_path):
19+
(tmp_path / ".env").write_text(VALID_ENV_CONTENT, encoding="utf-8")
20+
errors = validate_env(dotenv_path=tmp_path / ".env")
21+
assert errors == []
22+
23+
24+
# AC-H2: DRY_RUN accepts true/True/TRUE/1/false/False/0
25+
@pytest.mark.parametrize("value", ["true", "True", "TRUE", "1", "false", "False", "0"])
26+
def test_h2_dry_run_valid_values(clean_env, tmp_path, value):
27+
content = VALID_ENV_CONTENT.replace("DRY_RUN=false", f"DRY_RUN={value}")
28+
(tmp_path / ".env").write_text(content, encoding="utf-8")
29+
errors = validate_env(dotenv_path=tmp_path / ".env")
30+
assert errors == []
31+
32+
33+
# AC-H3: DB_PORT boundary values 1 and 65535
34+
@pytest.mark.parametrize("port", [1, 65535])
35+
def test_h3_db_port_boundary(clean_env, tmp_path, port):
36+
content = VALID_ENV_CONTENT.replace("DB_PORT=5432", f"DB_PORT={port}")
37+
(tmp_path / ".env").write_text(content, encoding="utf-8")
38+
errors = validate_env(dotenv_path=tmp_path / ".env")
39+
assert errors == []
40+
41+
42+
# AC-H4: MAX_RETRIES=0 → OK
43+
def test_h4_max_retries_zero(clean_env, tmp_path):
44+
content = VALID_ENV_CONTENT.replace("MAX_RETRIES=3", "MAX_RETRIES=0")
45+
(tmp_path / ".env").write_text(content, encoding="utf-8")
46+
errors = validate_env(dotenv_path=tmp_path / ".env")
47+
assert errors == []
48+
49+
50+
# AC-S1: .env file doesn't exist → 8 FAIL lines, exit 1
51+
def test_s1_no_env_file(clean_env, tmp_path):
52+
errors = validate_env(dotenv_path=tmp_path / ".env")
53+
assert len(errors) == 8
54+
assert all(e.startswith("[FAIL]") for e in errors)
55+
56+
57+
# AC-S2: DB_HOST missing
58+
def test_s2_db_host_missing(clean_env, tmp_path):
59+
content = "\n".join(
60+
line for line in VALID_ENV_CONTENT.splitlines()
61+
if not line.startswith("DB_HOST=")
62+
) + "\n"
63+
(tmp_path / ".env").write_text(content, encoding="utf-8")
64+
errors = validate_env(dotenv_path=tmp_path / ".env")
65+
assert "[FAIL] DB_HOST: 未设置" in errors
66+
67+
68+
# AC-S3: DB_PASSWORD empty string
69+
def test_s3_db_password_empty(clean_env, tmp_path):
70+
content = VALID_ENV_CONTENT.replace("DB_PASSWORD=s3cret", "DB_PASSWORD=")
71+
(tmp_path / ".env").write_text(content, encoding="utf-8")
72+
errors = validate_env(dotenv_path=tmp_path / ".env")
73+
assert "[FAIL] DB_PASSWORD: 值为空字符串" in errors
74+
75+
76+
# AC-S4: DB_PORT non-integer
77+
def test_s4_db_port_not_int(clean_env, tmp_path):
78+
content = VALID_ENV_CONTENT.replace("DB_PORT=5432", "DB_PORT=abc")
79+
(tmp_path / ".env").write_text(content, encoding="utf-8")
80+
errors = validate_env(dotenv_path=tmp_path / ".env")
81+
assert '[FAIL] DB_PORT: 无法转换为整数(当前值:"abc")' in errors
82+
83+
84+
# AC-S5: DB_PORT out of range (0 and 65536)
85+
@pytest.mark.parametrize("port,expected_val", [(0, 0), (65536, 65536)])
86+
def test_s5_db_port_out_of_range(clean_env, tmp_path, port, expected_val):
87+
content = VALID_ENV_CONTENT.replace("DB_PORT=5432", f"DB_PORT={port}")
88+
(tmp_path / ".env").write_text(content, encoding="utf-8")
89+
errors = validate_env(dotenv_path=tmp_path / ".env")
90+
assert f"[FAIL] DB_PORT: 值超出范围(需满足 1 <= DB_PORT <= 65535,当前值:{expected_val})" in errors
91+
92+
93+
# AC-S6: BATCH_SIZE=0 and BATCH_SIZE=-1
94+
@pytest.mark.parametrize("size", [0, -1])
95+
def test_s6_batch_size_not_positive(clean_env, tmp_path, size):
96+
content = VALID_ENV_CONTENT.replace("BATCH_SIZE=500", f"BATCH_SIZE={size}")
97+
(tmp_path / ".env").write_text(content, encoding="utf-8")
98+
errors = validate_env(dotenv_path=tmp_path / ".env")
99+
assert "[FAIL] BATCH_SIZE: 值超出范围(需满足 BATCH_SIZE > 0)" in errors
100+
101+
102+
# AC-S7: DRY_RUN=yes → invalid bool
103+
def test_s7_dry_run_invalid(clean_env, tmp_path):
104+
content = VALID_ENV_CONTENT.replace("DRY_RUN=false", "DRY_RUN=yes")
105+
(tmp_path / ".env").write_text(content, encoding="utf-8")
106+
errors = validate_env(dotenv_path=tmp_path / ".env")
107+
assert '[FAIL] DRY_RUN: 非法布尔值(当前值:"yes",合法值为 true/false/1/0)' in errors
108+
109+
110+
# AC-S8: Multiple errors in order
111+
def test_s8_multiple_errors_in_order(clean_env, tmp_path):
112+
content = (
113+
"DB_HOST=localhost\n"
114+
"DB_PORT=abc\n"
115+
"DB_NAME=analytics\n"
116+
"DB_USER=admin\n"
117+
"DB_PASSWORD=\n"
118+
"BATCH_SIZE=-1\n"
119+
"MAX_RETRIES=3\n"
120+
"DRY_RUN=yes\n"
121+
)
122+
(tmp_path / ".env").write_text(content, encoding="utf-8")
123+
errors = validate_env(dotenv_path=tmp_path / ".env")
124+
assert len(errors) == 4
125+
assert '[FAIL] DB_PORT: 无法转换为整数(当前值:"abc")' == errors[0]
126+
assert "[FAIL] DB_PASSWORD: 值为空字符串" == errors[1]
127+
assert "[FAIL] BATCH_SIZE: 值超出范围(需满足 BATCH_SIZE > 0)" == errors[2]
128+
assert '[FAIL] DRY_RUN: 非法布尔值(当前值:"yes",合法值为 true/false/1/0)' == errors[3]
129+

0 commit comments

Comments
 (0)