wup 0.2.64__tar.gz → 0.2.66__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {wup-0.2.64/wup.egg-info → wup-0.2.66}/PKG-INFO +20 -9
  2. {wup-0.2.64 → wup-0.2.66}/README.md +6 -6
  3. {wup-0.2.64 → wup-0.2.66}/pyproject.toml +55 -3
  4. wup-0.2.66/tests/test_cli_bridge.py +35 -0
  5. wup-0.2.66/tests/test_control.py +18 -0
  6. wup-0.2.66/tests/test_endpoints_init_cli.py +68 -0
  7. wup-0.2.66/tests/test_status_data.py +34 -0
  8. wup-0.2.66/tests/test_sync.py +41 -0
  9. {wup-0.2.64 → wup-0.2.66}/tests/test_testql_monitor.py +53 -6
  10. {wup-0.2.64 → wup-0.2.66}/tests/test_wup.py +31 -0
  11. wup-0.2.66/tests/test_wup_generate.py +21 -0
  12. {wup-0.2.64 → wup-0.2.66}/wup/__init__.py +1 -1
  13. {wup-0.2.64 → wup-0.2.66}/wup/cli.py +125 -164
  14. wup-0.2.66/wup/cli_bridge.py +80 -0
  15. {wup-0.2.64 → wup-0.2.66}/wup/config.py +11 -5
  16. wup-0.2.66/wup/control.py +106 -0
  17. wup-0.2.66/wup/endpoints.py +44 -0
  18. wup-0.2.66/wup/generate.py +62 -0
  19. wup-0.2.66/wup/init_cli.py +60 -0
  20. {wup-0.2.64 → wup-0.2.66}/wup/models/config.py +3 -0
  21. {wup-0.2.64 → wup-0.2.66}/wup/monitoring_manifest.py +30 -5
  22. wup-0.2.66/wup/paths.py +16 -0
  23. wup-0.2.66/wup/status_data.py +103 -0
  24. wup-0.2.66/wup/sync.py +70 -0
  25. {wup-0.2.64 → wup-0.2.66}/wup/testing/handlers/health_handlers.py +11 -7
  26. {wup-0.2.64 → wup-0.2.66}/wup/testql_monitor.py +197 -32
  27. {wup-0.2.64 → wup-0.2.66}/wup/testql_watcher.py +51 -2
  28. wup-0.2.66/wup/validate.py +34 -0
  29. {wup-0.2.64 → wup-0.2.66/wup.egg-info}/PKG-INFO +20 -9
  30. {wup-0.2.64 → wup-0.2.66}/wup.egg-info/SOURCES.txt +15 -0
  31. wup-0.2.66/wup.egg-info/requires.txt +21 -0
  32. wup-0.2.64/wup.egg-info/requires.txt +0 -8
  33. {wup-0.2.64 → wup-0.2.66}/LICENSE +0 -0
  34. {wup-0.2.64 → wup-0.2.66}/setup.cfg +0 -0
  35. {wup-0.2.64 → wup-0.2.66}/tests/test_assistant.py +0 -0
  36. {wup-0.2.64 → wup-0.2.66}/tests/test_auto_detection.py +0 -0
  37. {wup-0.2.64 → wup-0.2.66}/tests/test_cli_filtering.py +0 -0
  38. {wup-0.2.64 → wup-0.2.66}/tests/test_e2e.py +0 -0
  39. {wup-0.2.64 → wup-0.2.66}/tests/test_health_summary_passed.py +0 -0
  40. {wup-0.2.64 → wup-0.2.66}/tests/test_monitoring_manifest.py +0 -0
  41. {wup-0.2.64 → wup-0.2.66}/tests/test_probe_mutex.py +0 -0
  42. {wup-0.2.64 → wup-0.2.66}/tests/test_service_inference.py +0 -0
  43. {wup-0.2.64 → wup-0.2.66}/tests/test_testql_watcher.py +0 -0
  44. {wup-0.2.64 → wup-0.2.66}/tests/test_visual_diff_periodic_skip.py +0 -0
  45. {wup-0.2.64 → wup-0.2.66}/tests/test_visual_diff_progress.py +0 -0
  46. {wup-0.2.64 → wup-0.2.66}/tests/test_watch_exclude.py +0 -0
  47. {wup-0.2.64 → wup-0.2.66}/tests/test_web_client.py +0 -0
  48. {wup-0.2.64 → wup-0.2.66}/wup/_ast_detector.py +0 -0
  49. {wup-0.2.64 → wup-0.2.66}/wup/_base_detector.py +0 -0
  50. {wup-0.2.64 → wup-0.2.66}/wup/_hash_detector.py +0 -0
  51. {wup-0.2.64 → wup-0.2.66}/wup/_yaml_detector.py +0 -0
  52. {wup-0.2.64 → wup-0.2.66}/wup/anomaly_detector.py +0 -0
  53. {wup-0.2.64 → wup-0.2.66}/wup/anomaly_models.py +0 -0
  54. {wup-0.2.64 → wup-0.2.66}/wup/assistant.py +0 -0
  55. {wup-0.2.64 → wup-0.2.66}/wup/assistant_discovery.py +0 -0
  56. {wup-0.2.64 → wup-0.2.66}/wup/assistant_validator.py +0 -0
  57. {wup-0.2.64 → wup-0.2.66}/wup/bus.py +0 -0
  58. {wup-0.2.64 → wup-0.2.66}/wup/cli_config_generator.py +0 -0
  59. {wup-0.2.64 → wup-0.2.66}/wup/cli_scanner.py +0 -0
  60. {wup-0.2.64 → wup-0.2.66}/wup/core.py +0 -0
  61. {wup-0.2.64 → wup-0.2.66}/wup/dependency_mapper.py +0 -0
  62. {wup-0.2.64 → wup-0.2.66}/wup/event_store.py +0 -0
  63. {wup-0.2.64 → wup-0.2.66}/wup/file_watcher/events/file_events.py +0 -0
  64. {wup-0.2.64 → wup-0.2.66}/wup/models/__init__.py +0 -0
  65. {wup-0.2.64 → wup-0.2.66}/wup/planfile_reporter.py +0 -0
  66. {wup-0.2.64 → wup-0.2.66}/wup/testing/events/health_events.py +0 -0
  67. {wup-0.2.64 → wup-0.2.66}/wup/testing/events/test_results.py +0 -0
  68. {wup-0.2.64 → wup-0.2.66}/wup/testing/handlers/event_handlers.py +0 -0
  69. {wup-0.2.64 → wup-0.2.66}/wup/testing/queries/health_queries.py +0 -0
  70. {wup-0.2.64 → wup-0.2.66}/wup/testql_cli_generator.py +0 -0
  71. {wup-0.2.64 → wup-0.2.66}/wup/testql_discovery.py +0 -0
  72. {wup-0.2.64 → wup-0.2.66}/wup/visual_diff.py +0 -0
  73. {wup-0.2.64 → wup-0.2.66}/wup/web_client.py +0 -0
  74. {wup-0.2.64 → wup-0.2.66}/wup.egg-info/dependency_links.txt +0 -0
  75. {wup-0.2.64 → wup-0.2.66}/wup.egg-info/entry_points.txt +0 -0
  76. {wup-0.2.64 → wup-0.2.66}/wup.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wup
3
- Version: 0.2.64
3
+ Version: 0.2.66
4
4
  Summary: WUP (What's Up) - Intelligent file watcher for regression testing in large projects
5
5
  Author-email: Tom Sapletta <tom@sapletta.com>
6
6
  License-Expression: Apache-2.0
@@ -10,11 +10,10 @@ Keywords: wup,watcher,testing,regression,file-monitoring
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
- Requires-Python: >=3.9
16
+ Requires-Python: >=3.10
18
17
  Description-Content-Type: text/markdown
19
18
  License-File: LICENSE
20
19
  Requires-Dist: watchdog>=4.0.0
@@ -22,6 +21,18 @@ Requires-Dist: psutil>=5.9.0
22
21
  Requires-Dist: rich>=13.0.0
23
22
  Requires-Dist: typer>=0.9.0
24
23
  Requires-Dist: pyyaml>=6.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
26
+ Requires-Dist: goal>=2.1.0; extra == "dev"
27
+ Requires-Dist: costs>=0.1.20; extra == "dev"
28
+ Requires-Dist: pfix>=0.1.60; extra == "dev"
29
+ Requires-Dist: uri2wup; extra == "dev"
30
+ Requires-Dist: dsl2wup; extra == "dev"
31
+ Requires-Dist: nlp2wup; extra == "dev"
32
+ Requires-Dist: cli2wup; extra == "dev"
33
+ Requires-Dist: mcp2wup; extra == "dev"
34
+ Requires-Dist: rest2wup; extra == "dev"
35
+ Requires-Dist: httpx>=0.27; extra == "dev"
25
36
  Provides-Extra: visual
26
37
  Requires-Dist: playwright<2,>=1.40; extra == "visual"
27
38
  Dynamic: license-file
@@ -31,17 +42,17 @@ Dynamic: license-file
31
42
 
32
43
  ## AI Cost Tracking
33
44
 
34
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.64-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
35
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.49-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-31.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
45
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.66-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
46
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.34-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-35.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
36
47
 
37
- - 🤖 **LLM usage:** $3.4940 (76 commits)
38
- - 👤 **Human dev:** ~$3168 (31.7h @ $100/h, 30min dedup)
48
+ - 🤖 **LLM usage:** $3.3376 (79 commits)
49
+ - 👤 **Human dev:** ~$3518 (35.2h @ $100/h, 30min dedup)
39
50
 
40
- Generated on 2026-05-27 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
51
+ Generated on 2026-06-08 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
41
52
 
42
53
  ---
43
54
 
44
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.64-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
55
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.66-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
45
56
 
46
57
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
47
58
 
@@ -3,17 +3,17 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.64-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.49-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-31.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.66-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.34-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-35.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $3.4940 (76 commits)
10
- - 👤 **Human dev:** ~$3168 (31.7h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $3.3376 (79 commits)
10
+ - 👤 **Human dev:** ~$3518 (35.2h @ $100/h, 30min dedup)
11
11
 
12
- Generated on 2026-05-27 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
12
+ Generated on 2026-06-08 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
16
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.64-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
16
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.66-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
17
17
 
18
18
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
19
19
 
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wup"
7
- version = "0.2.64"
7
+ version = "0.2.66"
8
8
  description = "WUP (What's Up) - Intelligent file watcher for regression testing in large projects"
9
9
  readme = "README.md"
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.10"
11
11
  authors = [
12
12
  {name = "Tom Sapletta", email = "tom@sapletta.com"},
13
13
  ]
@@ -23,7 +23,6 @@ classifiers = [
23
23
  "Development Status :: 3 - Alpha",
24
24
  "Intended Audience :: Developers",
25
25
  "Programming Language :: Python :: 3",
26
- "Programming Language :: Python :: 3.9",
27
26
  "Programming Language :: Python :: 3.10",
28
27
  "Programming Language :: Python :: 3.11",
29
28
  "Programming Language :: Python :: 3.12",
@@ -31,10 +30,63 @@ classifiers = [
31
30
  license = "Apache-2.0"
32
31
 
33
32
  [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=7.0.0",
35
+ "goal>=2.1.0",
36
+ "costs>=0.1.20",
37
+ "pfix>=0.1.60",
38
+ "uri2wup",
39
+ "dsl2wup",
40
+ "nlp2wup",
41
+ "cli2wup",
42
+ "mcp2wup",
43
+ "rest2wup",
44
+ "httpx>=0.27",
45
+ ]
34
46
  visual = [
35
47
  "playwright>=1.40,<2",
36
48
  ]
37
49
 
50
+ [dependency-groups]
51
+ dev = [
52
+ "pytest>=7.0.0",
53
+ "goal>=2.1.0",
54
+ "costs>=0.1.20",
55
+ "pfix>=0.1.60",
56
+ "uri2wup",
57
+ "dsl2wup",
58
+ "nlp2wup",
59
+ "cli2wup",
60
+ "mcp2wup",
61
+ "rest2wup",
62
+ "httpx>=0.27",
63
+ ]
64
+
65
+ [tool.uv.workspace]
66
+ members = [
67
+ ".",
68
+ "packages/uri2wup",
69
+ "packages/dsl2wup",
70
+ "packages/nlp2wup",
71
+ "packages/cli2wup",
72
+ "packages/mcp2wup",
73
+ "packages/rest2wup",
74
+ ]
75
+
76
+ [tool.uv.sources]
77
+ wup = { workspace = true }
78
+ uri2wup = { workspace = true }
79
+ dsl2wup = { workspace = true }
80
+ nlp2wup = { workspace = true }
81
+ cli2wup = { workspace = true }
82
+ mcp2wup = { workspace = true }
83
+ rest2wup = { workspace = true }
84
+
85
+ [tool.pytest.ini_options]
86
+ testpaths = ["tests", "packages"]
87
+ python_files = ["test_*.py"]
88
+ addopts = "-ra --tb=short"
89
+
38
90
  [tool.setuptools.packages.find]
39
91
  include = ["wup*"]
40
92
 
@@ -0,0 +1,35 @@
1
+ """Tests for wup.cli_bridge → bus delegation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from wup.cli_bridge import run_init, run_map_deps, run_validate
8
+
9
+
10
+ def test_bridge_init(tmp_path: Path) -> None:
11
+ result = run_init(project=str(tmp_path), out="wup.yaml")
12
+ assert result["ok"]
13
+ assert (tmp_path / "wup.yaml").exists()
14
+
15
+
16
+ def test_bridge_map_deps(tmp_path: Path) -> None:
17
+ (tmp_path / "app.py").write_text("from fastapi import FastAPI\napp = FastAPI()\n", encoding="utf-8")
18
+ (tmp_path / "wup.yaml").write_text(
19
+ "project:\n name: demo\nwatch:\n paths: []\nservices: []\n",
20
+ encoding="utf-8",
21
+ )
22
+ result = run_map_deps(project=str(tmp_path), out="deps.json", framework="auto")
23
+ assert result["ok"]
24
+ assert (tmp_path / "deps.json").exists()
25
+
26
+
27
+ def test_bridge_validate(tmp_path: Path) -> None:
28
+ config = tmp_path / "wup.yaml"
29
+ config.write_text(
30
+ "project:\n name: demo\n description: test\nwatch:\n paths: []\n"
31
+ "services:\n - name: api\n type: web\n",
32
+ encoding="utf-8",
33
+ )
34
+ result = run_validate(path=str(config), project=str(tmp_path))
35
+ assert "ok" in result
@@ -0,0 +1,18 @@
1
+ """Tests for wup.control shim."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from wup.control import dispatch_validate
8
+
9
+
10
+ def test_dispatch_validate_shim(tmp_path: Path) -> None:
11
+ config = tmp_path / "wup.yaml"
12
+ config.write_text(
13
+ "project:\n name: demo\n description: test\nwatch:\n paths: []\nservices: []\n",
14
+ encoding="utf-8",
15
+ )
16
+ result = dispatch_validate(str(config), project=str(tmp_path))
17
+ assert "ok" in result
18
+ assert result["action"] == "validate"
@@ -0,0 +1,68 @@
1
+ """Tests for endpoints + init_cli domain and bus."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+
9
+ from dsl2wup import dispatch
10
+ from wup.endpoints import discover_testql_endpoints
11
+ from wup.init_cli import setup_cli_project
12
+
13
+
14
+ def _write_scenario(dir_path: Path, name: str = "smoke.testql.toon.yaml") -> Path:
15
+ dir_path.mkdir(parents=True, exist_ok=True)
16
+ scenario = dir_path / name
17
+ scenario.write_text(
18
+ yaml.safe_dump(
19
+ {
20
+ "name": "smoke",
21
+ "service": "api",
22
+ "steps": [{"request": {"method": "GET", "url": "/health"}}],
23
+ }
24
+ ),
25
+ encoding="utf-8",
26
+ )
27
+ return scenario
28
+
29
+
30
+ def test_discover_testql_endpoints(tmp_path: Path) -> None:
31
+ scen = tmp_path / "scenarios"
32
+ _write_scenario(scen)
33
+ result = discover_testql_endpoints(scen, out=tmp_path / "deps.json")
34
+ assert result["ok"]
35
+ assert (tmp_path / "deps.json").exists()
36
+
37
+
38
+ def test_endpoints_via_bus(tmp_path: Path) -> None:
39
+ scen = tmp_path / "scenarios"
40
+ _write_scenario(scen)
41
+ result = dispatch(f"ENDPOINTS {scen} OUT {tmp_path / 'out.json'}")
42
+ assert result.ok
43
+ assert (tmp_path / "out.json").exists()
44
+
45
+
46
+ def test_init_cli_via_bus(tmp_path: Path) -> None:
47
+ (tmp_path / "pyproject.toml").write_text(
48
+ '[project]\nname = "demo"\n\n[project.scripts]\n demo = "demo:main"\n',
49
+ encoding="utf-8",
50
+ )
51
+ (tmp_path / "demo").mkdir()
52
+ (tmp_path / "demo" / "__init__.py").write_text("def main(): pass\n", encoding="utf-8")
53
+ result = dispatch(f"INIT_CLI {tmp_path} OUT wup.yaml SCENARIOS testql-scenarios")
54
+ assert result.ok
55
+ assert (tmp_path / "wup.yaml").exists()
56
+ assert (tmp_path / "testql-scenarios").is_dir()
57
+
58
+
59
+ def test_setup_cli_project_core(tmp_path: Path) -> None:
60
+ (tmp_path / "pyproject.toml").write_text(
61
+ '[project]\nname = "demo"\n\n[project.scripts]\n demo = "demo:main"\n',
62
+ encoding="utf-8",
63
+ )
64
+ (tmp_path / "demo").mkdir()
65
+ (tmp_path / "demo" / "__init__.py").write_text("def main(): pass\n", encoding="utf-8")
66
+ payload = setup_cli_project(tmp_path)
67
+ assert payload["ok"]
68
+ assert payload["commands"] >= 1
@@ -0,0 +1,34 @@
1
+ """Tests for STATUS snapshot."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from dsl2wup import dispatch
9
+ from wup.paths import health_state_path
10
+ from wup.status_data import collect_status_snapshot
11
+
12
+
13
+ def test_collect_status_snapshot(tmp_path: Path) -> None:
14
+ (tmp_path / "wup.yaml").write_text(
15
+ "project:\n name: demo\n description: t\nwatch:\n paths: []\nservices:\n - name: api\n type: web\n",
16
+ encoding="utf-8",
17
+ )
18
+ state = health_state_path(tmp_path)
19
+ state.parent.mkdir(parents=True, exist_ok=True)
20
+ state.write_text(json.dumps({"api": {"status": "up"}}), encoding="utf-8")
21
+ snap = collect_status_snapshot(tmp_path)
22
+ assert snap["ok"]
23
+ assert snap["project_name"] == "demo"
24
+ assert "api" in snap["health"]
25
+
26
+
27
+ def test_status_via_bus(tmp_path: Path) -> None:
28
+ (tmp_path / "wup.yaml").write_text(
29
+ "project:\n name: demo\nwatch:\n paths: []\nservices:\n - name: api\n type: web\n",
30
+ encoding="utf-8",
31
+ )
32
+ result = dispatch(f"STATUS PROJECT {tmp_path} FILE wup.yaml")
33
+ assert result.ok
34
+ assert result.data["project_name"] == "demo"
@@ -0,0 +1,41 @@
1
+ """Tests for wup.sync."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+
9
+ from wup.sync import sync_testql_manifest
10
+
11
+
12
+ def _minimal_config(path: Path) -> None:
13
+ path.write_text(
14
+ yaml.safe_dump(
15
+ {
16
+ "project": {"name": "demo", "description": "test"},
17
+ "watch": {"paths": [], "exclude_patterns": [], "file_types": []},
18
+ "services": [{"name": "api", "type": "web"}],
19
+ "testql": {"scenario_dir": "scenarios", "endpoints_by_service": {"api": ["/health"]}},
20
+ },
21
+ sort_keys=False,
22
+ ),
23
+ encoding="utf-8",
24
+ )
25
+
26
+
27
+ def test_sync_writes_manifest(tmp_path: Path) -> None:
28
+ cfg = tmp_path / "wup.yaml"
29
+ _minimal_config(cfg)
30
+ result = sync_testql_manifest(tmp_path, config_file=str(cfg), write=True)
31
+ assert result["ok"]
32
+ text = cfg.read_text(encoding="utf-8")
33
+ assert "BEGIN WUP MONITORING MANIFEST" in text
34
+
35
+
36
+ def test_sync_merge_endpoints_flag(tmp_path: Path) -> None:
37
+ cfg = tmp_path / "wup.yaml"
38
+ _minimal_config(cfg)
39
+ result = sync_testql_manifest(tmp_path, config_file=str(cfg), merge_endpoints=True, write=True)
40
+ assert result["ok"]
41
+ assert "merge_endpoints" in result
@@ -34,11 +34,48 @@ API[1]{method, endpoint, expected_status}:
34
34
  assert is_monitoring_probe(probes[0])
35
35
 
36
36
 
37
- def test_firmware_plugin_health_on_8202_not_live_probe():
37
+ def test_hardware_identify_and_peripheral_status_are_live_probes():
38
+ assert is_monitoring_probe(
39
+ ProbeTarget(url="http://localhost:8202/api/v1/hardware/identify")
40
+ )
41
+ assert is_monitoring_probe(
42
+ ProbeTarget(url="http://localhost:8096/api/v3/hardware/peripheral-status/modbus-io")
43
+ )
44
+
45
+
46
+ def test_firmware_plugin_health_catalog_not_periodic_live_probe():
47
+ """Plugin health is listed for detail TestQL; live watch uses identify + peripheral-status."""
38
48
  probe = ProbeTarget(url="http://localhost:8202/api/v1/plugins/modbus-io/health")
39
- assert not is_monitoring_probe(probe)
40
- direct = ProbeTarget(url="http://localhost:8202/health")
41
- assert is_monitoring_probe(direct)
49
+ assert is_monitoring_probe(probe)
50
+ with tempfile.TemporaryDirectory() as tmpdir:
51
+ root = Path(tmpdir)
52
+ cfg = WupConfig(
53
+ project=ProjectConfig(name="demo"),
54
+ services=[
55
+ ServiceConfig(name="firmware", paths=["backend/firmware/**"]),
56
+ ServiceConfig(name="connect-scenario", paths=["connect-scenario/**"]),
57
+ ],
58
+ watch=WatchConfig(),
59
+ testql=TestQLConfig(
60
+ hardware_usb_modules={
61
+ "oqlos_url": "http://localhost:8202",
62
+ "proxy_url": "http://localhost:8096",
63
+ "module_ids": ["modbus-io", "modbus-adc"],
64
+ },
65
+ ),
66
+ )
67
+ monitor = TestQLMonitor(root, cfg)
68
+ firmware = {p.url for p in monitor.probes_for_service("firmware")}
69
+ scenario = {p.url for p in monitor.probes_for_service("connect-scenario")}
70
+ assert "http://localhost:8202/api/v1/hardware/identify" in firmware
71
+ assert "http://localhost:8202/api/v1/plugins/modbus-io/health" not in firmware
72
+ assert "http://localhost:8096/api/v3/hardware/identify" in scenario
73
+ assert (
74
+ "http://localhost:8096/api/v3/hardware/peripheral-status/modbus-io" in scenario
75
+ )
76
+ assert (
77
+ "http://localhost:8096/api/v3/hardware/peripheral-status/modbus-adc" in scenario
78
+ )
42
79
 
43
80
 
44
81
  def test_connect_api_paths_on_8100_are_not_monitoring_probes():
@@ -108,8 +145,18 @@ def test_monitor_merges_config_and_service_map():
108
145
  probes = monitor.probes_for_service("firmware")
109
146
  urls = {p.url for p in probes}
110
147
  assert "http://localhost:8100/firmware/api/v1/health" in urls
111
- assert "http://localhost:8100/firmware/api/v1/execution/status" in urls
112
- assert "http://localhost:8100/firmware/api/v1/execution/logs" in urls
148
+ # Execution telemetry is for fleet TestQL, not WUP live liveness probes.
149
+ assert "http://localhost:8100/firmware/api/v1/execution/status" not in urls
150
+ assert "http://localhost:8100/firmware/api/v1/execution/logs" not in urls
151
+
152
+
153
+ def test_firmware_live_probe_prefers_oqlos_8202():
154
+ probes = [
155
+ ProbeTarget(url="http://localhost:8100/firmware/api/v1/health"),
156
+ ProbeTarget(url="http://localhost:8202/health"),
157
+ ]
158
+ ordered = TestQLMonitor._sort_probes_for_live(probes, service="firmware")
159
+ assert ordered[0].url == "http://localhost:8202/health"
113
160
 
114
161
 
115
162
  def test_probes_for_service_ignores_non_health_extra_paths():
@@ -1790,6 +1790,37 @@ class TestTestQLWatcherConfig:
1790
1790
  scenarios = watcher._select_scenarios_for_service("users")
1791
1791
  # Should be limited by config when no matching scenarios found
1792
1792
  assert len(scenarios) <= 2
1793
+
1794
+ def test_testql_watcher_select_scenarios_uses_pinned_scenario(self):
1795
+ """Pinned quick_tests.scenario wins over auto-api scoring."""
1796
+ with tempfile.TemporaryDirectory() as tmpdir:
1797
+ scenarios_dir = Path(tmpdir) / "testql-scenarios"
1798
+ scenarios_dir.mkdir()
1799
+ (scenarios_dir / "auto-api-connect-workshop.testql.toon.yaml").write_text("# workshop")
1800
+ pinned = scenarios_dir / "connect-scenario-wup-quick-fast.testql.toon.yaml"
1801
+ pinned.write_text("# sentinel")
1802
+
1803
+ service = ServiceConfig(
1804
+ name="connect-scenario",
1805
+ type="web",
1806
+ paths=["connect-scenario/**"],
1807
+ quick_tests=ServiceTestConfig(
1808
+ scope="all",
1809
+ max_endpoints=1,
1810
+ scenario="connect-scenario-wup-quick-fast.testql.toon.yaml",
1811
+ ),
1812
+ )
1813
+ config = WupConfig(
1814
+ project=ProjectConfig(name="test"),
1815
+ watch=WatchConfig(),
1816
+ services=[service],
1817
+ test_strategy=TestStrategyConfig(),
1818
+ testql=TestQLConfig(),
1819
+ )
1820
+
1821
+ watcher = TestQLWatcher(tmpdir, scenarios_dir="testql-scenarios", config=config)
1822
+ selected = watcher._select_scenarios_for_service("connect-scenario")
1823
+ assert selected == [pinned]
1793
1824
 
1794
1825
  def test_testql_watcher_uses_config_timeout(self):
1795
1826
  """Test that TestQLWatcher uses config timeout settings."""
@@ -0,0 +1,21 @@
1
+ """Tests for wup.generate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from wup.generate import generate_wup_config
8
+
9
+
10
+ def test_generate_fastapi_config(tmp_path: Path) -> None:
11
+ result = generate_wup_config(tmp_path, hint="fastapi project", out="wup.yaml")
12
+ assert result["ok"]
13
+ assert (tmp_path / "wup.yaml").exists()
14
+ assert result["framework"] == "fastapi"
15
+ assert result["services"] >= 1
16
+
17
+
18
+ def test_generate_refuses_existing_without_overwrite(tmp_path: Path) -> None:
19
+ (tmp_path / "wup.yaml").write_text("project:\n name: x\n", encoding="utf-8")
20
+ result = generate_wup_config(tmp_path, hint="flask", out="wup.yaml")
21
+ assert not result["ok"]
@@ -7,7 +7,7 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
7
7
  3. Detail Layer: Full tests with blame reports (only on failure)
8
8
  """
9
9
 
10
- __version__ = "0.2.64"
10
+ __version__ = "0.2.66"
11
11
  __author__ = "Tom Sapletta"
12
12
 
13
13
  from .config import load_config, save_config, get_default_config