wup 0.2.25__tar.gz → 0.2.26__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.
- {wup-0.2.25/wup.egg-info → wup-0.2.26}/PKG-INFO +7 -7
- {wup-0.2.25 → wup-0.2.26}/README.md +6 -6
- {wup-0.2.25 → wup-0.2.26}/pyproject.toml +1 -1
- wup-0.2.26/tests/test_monitoring_manifest.py +72 -0
- wup-0.2.26/tests/test_testql_monitor.py +121 -0
- {wup-0.2.25 → wup-0.2.26}/wup/__init__.py +1 -1
- {wup-0.2.25 → wup-0.2.26}/wup/cli.py +133 -2
- {wup-0.2.25 → wup-0.2.26}/wup/config.py +6 -0
- {wup-0.2.25 → wup-0.2.26}/wup/models/config.py +4 -1
- wup-0.2.26/wup/monitoring_manifest.py +306 -0
- wup-0.2.26/wup/testql_monitor.py +329 -0
- {wup-0.2.25 → wup-0.2.26}/wup/testql_watcher.py +129 -0
- {wup-0.2.25 → wup-0.2.26/wup.egg-info}/PKG-INFO +7 -7
- {wup-0.2.25 → wup-0.2.26}/wup.egg-info/SOURCES.txt +4 -0
- {wup-0.2.25 → wup-0.2.26}/LICENSE +0 -0
- {wup-0.2.25 → wup-0.2.26}/setup.cfg +0 -0
- {wup-0.2.25 → wup-0.2.26}/tests/test_e2e.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/tests/test_testql_watcher.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/tests/test_web_client.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/tests/test_wup.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/_ast_detector.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/_hash_detector.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/_yaml_detector.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/anomaly_detector.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/anomaly_models.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/assistant.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/core.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/dependency_mapper.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/models/__init__.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/testql_discovery.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/visual_diff.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup/web_client.py +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup.egg-info/dependency_links.txt +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup.egg-info/entry_points.txt +0 -0
- {wup-0.2.25 → wup-0.2.26}/wup.egg-info/requires.txt +0 -0
- {wup-0.2.25 → wup-0.2.26}/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.
|
|
3
|
+
Version: 0.2.26
|
|
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
|
|
@@ -29,17 +29,17 @@ Dynamic: license-file
|
|
|
29
29
|
|
|
30
30
|
## AI Cost Tracking
|
|
31
31
|
|
|
32
|
-
    
|
|
33
|
+
  
|
|
34
34
|
|
|
35
|
-
- 🤖 **LLM usage:** $1.
|
|
36
|
-
- 👤 **Human dev:** ~$
|
|
35
|
+
- 🤖 **LLM usage:** $1.9540 (36 commits)
|
|
36
|
+
- 👤 **Human dev:** ~$1532 (15.3h @ $100/h, 30min dedup)
|
|
37
37
|
|
|
38
|
-
Generated on 2026-05-
|
|
38
|
+
Generated on 2026-05-16 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
-
    
|
|
43
43
|
|
|
44
44
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
45
45
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $1.
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $1.9540 (36 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$1532 (15.3h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
|
-
Generated on 2026-05-
|
|
12
|
+
Generated on 2026-05-16 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
    
|
|
17
17
|
|
|
18
18
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
19
19
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from wup.models.config import ProjectConfig, ServiceConfig, TestQLConfig, WupConfig, WatchConfig
|
|
7
|
+
from wup.monitoring_manifest import (
|
|
8
|
+
MANIFEST_BEGIN,
|
|
9
|
+
MANIFEST_END,
|
|
10
|
+
build_monitoring_manifest,
|
|
11
|
+
discover_docker_compose_services,
|
|
12
|
+
load_monitoring_manifest_from_yaml,
|
|
13
|
+
patch_wup_yaml_monitoring,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_discover_docker_compose():
|
|
18
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
19
|
+
root = Path(tmpdir)
|
|
20
|
+
(root / "docker-compose.yml").write_text(
|
|
21
|
+
"services:\n"
|
|
22
|
+
" firmware:\n"
|
|
23
|
+
" container_name: test-simulator-firmware\n"
|
|
24
|
+
" ports:\n"
|
|
25
|
+
" - '8202:8202'\n"
|
|
26
|
+
" healthcheck:\n"
|
|
27
|
+
" test: ['CMD', 'curl', 'http://127.0.0.1:8202/health']\n",
|
|
28
|
+
encoding="utf-8",
|
|
29
|
+
)
|
|
30
|
+
found = discover_docker_compose_services(root)
|
|
31
|
+
assert len(found) == 1
|
|
32
|
+
assert found[0].compose_service == "firmware"
|
|
33
|
+
assert found[0].container_name == "test-simulator-firmware"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_patch_and_load_monitoring_block():
|
|
37
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
38
|
+
root = Path(tmpdir)
|
|
39
|
+
(root / "docker-compose.yml").write_text(
|
|
40
|
+
"services:\n firmware:\n container_name: test-simulator-firmware\n ports:\n - '8202:8202'\n",
|
|
41
|
+
encoding="utf-8",
|
|
42
|
+
)
|
|
43
|
+
cfg_path = root / "wup.yaml"
|
|
44
|
+
cfg_path.write_text(
|
|
45
|
+
"project:\n name: demo\n"
|
|
46
|
+
"services:\n - name: firmware\n paths: ['backend/firmware/**']\n"
|
|
47
|
+
"testql:\n base_url: http://localhost:8100\n"
|
|
48
|
+
" endpoints_by_service:\n firmware:\n - /firmware/api/v1/health\n",
|
|
49
|
+
encoding="utf-8",
|
|
50
|
+
)
|
|
51
|
+
wup_config = WupConfig(
|
|
52
|
+
project=ProjectConfig(name="demo"),
|
|
53
|
+
services=[ServiceConfig(name="firmware", paths=["backend/firmware/**"])],
|
|
54
|
+
watch=WatchConfig(),
|
|
55
|
+
testql=TestQLConfig(
|
|
56
|
+
base_url="http://localhost:8100",
|
|
57
|
+
endpoints_by_service={"firmware": ["/firmware/api/v1/health"]},
|
|
58
|
+
probe_interval_s=60,
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
manifest = build_monitoring_manifest(root, wup_config)
|
|
62
|
+
patch_wup_yaml_monitoring(cfg_path, manifest)
|
|
63
|
+
|
|
64
|
+
text = cfg_path.read_text(encoding="utf-8")
|
|
65
|
+
assert MANIFEST_BEGIN in text
|
|
66
|
+
assert MANIFEST_END in text
|
|
67
|
+
|
|
68
|
+
loaded = load_monitoring_manifest_from_yaml(cfg_path)
|
|
69
|
+
assert loaded is not None
|
|
70
|
+
assert loaded["wup_services"]["firmware"]["live_probes"]
|
|
71
|
+
assert loaded["wup_services"]["firmware"]["live_probes"][0]["url"].endswith("/firmware/api/v1/health")
|
|
72
|
+
assert loaded["wup_services"]["firmware"]["docker"][0]["compose_service"] == "firmware"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
from wup.models.config import ProjectConfig, ServiceConfig, TestQLConfig, WupConfig, WatchConfig
|
|
8
|
+
from wup.testql_monitor import (
|
|
9
|
+
ProbeTarget,
|
|
10
|
+
TestQLMonitor,
|
|
11
|
+
assign_probe_to_service,
|
|
12
|
+
is_monitoring_probe,
|
|
13
|
+
parse_scenario_probes,
|
|
14
|
+
parse_service_map_probes,
|
|
15
|
+
)
|
|
16
|
+
from wup.testql_watcher import TestQLWatcher
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_parse_scenario_probes_full_url():
|
|
20
|
+
content = """
|
|
21
|
+
API[1]{method, endpoint, expected_status}:
|
|
22
|
+
GET, http://localhost:8100/firmware/api/v1/health, 200
|
|
23
|
+
"""
|
|
24
|
+
with tempfile.NamedTemporaryFile("w", suffix=".testql.toon.yaml", delete=False) as handle:
|
|
25
|
+
handle.write(content)
|
|
26
|
+
handle.flush()
|
|
27
|
+
path = Path(handle.name)
|
|
28
|
+
|
|
29
|
+
probes = parse_scenario_probes(path)
|
|
30
|
+
path.unlink(missing_ok=True)
|
|
31
|
+
assert len(probes) == 1
|
|
32
|
+
assert probes[0].url.endswith("/firmware/api/v1/health")
|
|
33
|
+
assert probes[0].expected_status == 200
|
|
34
|
+
assert is_monitoring_probe(probes[0])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_assign_firmware_service():
|
|
38
|
+
services = [
|
|
39
|
+
ServiceConfig(name="frontend", paths=["frontend/**"]),
|
|
40
|
+
ServiceConfig(name="firmware", paths=["backend/firmware/**"]),
|
|
41
|
+
]
|
|
42
|
+
probe = ProbeTarget(url="http://localhost:8100/firmware/api/v1/health")
|
|
43
|
+
assert assign_probe_to_service(probe, services) == "firmware"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_monitor_merges_config_and_service_map():
|
|
47
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
48
|
+
root = Path(tmpdir)
|
|
49
|
+
scenario_dir = root / "testql-scenarios"
|
|
50
|
+
scenario_dir.mkdir()
|
|
51
|
+
scenario = scenario_dir / "fleet-health.testql.toon.yaml"
|
|
52
|
+
scenario.write_text(
|
|
53
|
+
"API[1]{method, endpoint, expected_status}:\n"
|
|
54
|
+
" GET, http://localhost:8100/firmware/api/v1/execution/status, 200\n",
|
|
55
|
+
encoding="utf-8",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
service_map = root / "service-map.yaml"
|
|
59
|
+
service_map.write_text(
|
|
60
|
+
"service:\n base_url: http://localhost:8100\n"
|
|
61
|
+
"endpoints:\n"
|
|
62
|
+
" - { method: GET, path: /firmware/api/v1/health }\n",
|
|
63
|
+
encoding="utf-8",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
cfg = WupConfig(
|
|
67
|
+
project=ProjectConfig(name="demo"),
|
|
68
|
+
services=[
|
|
69
|
+
ServiceConfig(name="firmware", paths=["backend/firmware/**"]),
|
|
70
|
+
],
|
|
71
|
+
watch=WatchConfig(),
|
|
72
|
+
testql=TestQLConfig(
|
|
73
|
+
scenario_dir="testql-scenarios",
|
|
74
|
+
base_url="http://localhost:8100",
|
|
75
|
+
endpoint_discovery=True,
|
|
76
|
+
service_map_globs=["service-map.yaml"],
|
|
77
|
+
endpoints_by_service={
|
|
78
|
+
"firmware": ["/firmware/api/v1/execution/logs"],
|
|
79
|
+
},
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
monitor = TestQLMonitor(root, cfg)
|
|
83
|
+
probes = monitor.probes_for_service("firmware")
|
|
84
|
+
urls = {p.url for p in probes}
|
|
85
|
+
assert "http://localhost:8100/firmware/api/v1/health" in urls
|
|
86
|
+
assert "http://localhost:8100/firmware/api/v1/execution/status" in urls
|
|
87
|
+
assert "http://localhost:8100/firmware/api/v1/execution/logs" in urls
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_live_probe_failure_updates_health():
|
|
91
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
92
|
+
root = Path(tmpdir)
|
|
93
|
+
cfg = WupConfig(
|
|
94
|
+
project=ProjectConfig(name="demo"),
|
|
95
|
+
services=[ServiceConfig(name="firmware", paths=["backend/firmware/**"])],
|
|
96
|
+
watch=WatchConfig(),
|
|
97
|
+
testql=TestQLConfig(
|
|
98
|
+
scenario_dir="testql-scenarios",
|
|
99
|
+
base_url="http://localhost:8100",
|
|
100
|
+
endpoints_by_service={"firmware": ["/firmware/api/v1/health"]},
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
watcher = TestQLWatcher(
|
|
104
|
+
project_root=str(root),
|
|
105
|
+
deps_file=str(root / "deps.json"),
|
|
106
|
+
scenarios_dir="testql-scenarios",
|
|
107
|
+
config=cfg,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
failing = ProbeTarget(url="http://localhost:8100/firmware/api/v1/health")
|
|
111
|
+
|
|
112
|
+
def fake_probe(self, timeout_s=10.0):
|
|
113
|
+
return False, "HTTP 500 (expected 200)"
|
|
114
|
+
|
|
115
|
+
with patch.object(ProbeTarget, "probe", fake_probe):
|
|
116
|
+
ok = asyncio.run(watcher._run_live_http_probes("firmware", []))
|
|
117
|
+
|
|
118
|
+
assert ok is False
|
|
119
|
+
state = json.loads((root / ".wup" / "service-health.json").read_text(encoding="utf-8"))
|
|
120
|
+
assert state["firmware"]["status"] == "down"
|
|
121
|
+
assert state["firmware"]["stage"] == "probe"
|
|
@@ -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.
|
|
10
|
+
__version__ = "0.2.26"
|
|
11
11
|
__author__ = "Tom Sapletta"
|
|
12
12
|
|
|
13
13
|
from .config import load_config, save_config, get_default_config
|
|
@@ -9,7 +9,7 @@ from typing import Optional
|
|
|
9
9
|
import typer
|
|
10
10
|
from rich.console import Console
|
|
11
11
|
|
|
12
|
-
from .config import load_config
|
|
12
|
+
from .config import find_config_file, load_config
|
|
13
13
|
from .core import WupWatcher
|
|
14
14
|
from .dependency_mapper import DependencyMapper
|
|
15
15
|
from .models.config import WupConfig
|
|
@@ -37,6 +37,11 @@ def watch(
|
|
|
37
37
|
browser_service_url: Optional[str] = typer.Option(None, "--browser-service-url", help="HTTP endpoint for browser notifications"),
|
|
38
38
|
track_dir: str = typer.Option(".wup/tracks", "--track-dir", help="Directory where error track JSON files are written"),
|
|
39
39
|
quick_limit: int = typer.Option(3, "--quick-limit", help="Maximum TestQL scenarios used in quick pass"),
|
|
40
|
+
probe_interval: Optional[int] = typer.Option(
|
|
41
|
+
None,
|
|
42
|
+
"--probe-interval",
|
|
43
|
+
help="Periodic live HTTP/TestQL probes in seconds (overrides testql.probe_interval_s)",
|
|
44
|
+
),
|
|
40
45
|
config: Optional[str] = typer.Option(None, "--config", "-C", help="Path to wup.yaml config file"),
|
|
41
46
|
):
|
|
42
47
|
"""
|
|
@@ -56,16 +61,30 @@ def watch(
|
|
|
56
61
|
# Load configuration
|
|
57
62
|
config_path = Path(config) if config else None
|
|
58
63
|
wup_config = load_config(project_path, config_path)
|
|
59
|
-
|
|
64
|
+
if probe_interval is not None:
|
|
65
|
+
wup_config.testql.probe_interval_s = int(probe_interval)
|
|
66
|
+
|
|
60
67
|
console.print(f"[bold cyan]🚀 WUP Watcher[/bold cyan]")
|
|
61
68
|
console.print(f"[dim]Project: {wup_config.project.name}[/dim]")
|
|
62
69
|
console.print(f"[dim]Description: {wup_config.project.description}[/dim]")
|
|
63
70
|
console.print(f"[dim]CPU Throttle: {cpu_throttle * 100}%[/dim]")
|
|
64
71
|
console.print(f"[dim]Debounce: {debounce}s[/dim]")
|
|
65
72
|
console.print(f"[dim]Cooldown: {cooldown}s[/dim]")
|
|
73
|
+
if wup_config.testql.probe_interval_s:
|
|
74
|
+
console.print(f"[dim]Live probes: every {wup_config.testql.probe_interval_s}s[/dim]")
|
|
66
75
|
console.print(f"[dim]Config: {config_path or 'auto-detected'}[/dim]")
|
|
67
76
|
console.print()
|
|
68
77
|
|
|
78
|
+
cfg_path = config_path if config_path and config_path.exists() else find_config_file(project_path)
|
|
79
|
+
if cfg_path:
|
|
80
|
+
from .monitoring_manifest import build_monitoring_manifest, patch_wup_yaml_monitoring
|
|
81
|
+
try:
|
|
82
|
+
manifest = build_monitoring_manifest(project_path, wup_config)
|
|
83
|
+
patch_wup_yaml_monitoring(cfg_path, manifest)
|
|
84
|
+
console.print("[dim]Refreshed monitoring manifest in wup.yaml[/dim]")
|
|
85
|
+
except OSError as exc:
|
|
86
|
+
console.print(f"[yellow]Could not refresh monitoring manifest: {exc}[/yellow]")
|
|
87
|
+
|
|
69
88
|
if mode.lower() == "testql":
|
|
70
89
|
watcher = TestQLWatcher(
|
|
71
90
|
project_root=str(project_path),
|
|
@@ -274,6 +293,33 @@ def status(
|
|
|
274
293
|
if track_file:
|
|
275
294
|
lines.append(Text.from_markup(f" [dim]track: {track_file}[/dim]"))
|
|
276
295
|
|
|
296
|
+
# --- monitoring manifest (from wup.yaml) ---
|
|
297
|
+
manifest_path = config_path if config_path and config_path.exists() else find_config_file(project_path)
|
|
298
|
+
if manifest_path:
|
|
299
|
+
from .monitoring_manifest import load_monitoring_manifest_from_yaml
|
|
300
|
+
|
|
301
|
+
manifest = load_monitoring_manifest_from_yaml(manifest_path)
|
|
302
|
+
if manifest:
|
|
303
|
+
lines.append(Text(""))
|
|
304
|
+
lines.append(Text.from_markup("[bold]Configured monitoring (wup.yaml):[/bold]"))
|
|
305
|
+
lines.append(Text.from_markup(
|
|
306
|
+
f" [dim]manifest {manifest.get('generated_at', '?')} · "
|
|
307
|
+
f"probe {manifest.get('probe_interval_s', 0)}s[/dim]"
|
|
308
|
+
))
|
|
309
|
+
for svc, info in sorted((manifest.get("wup_services") or {}).items()):
|
|
310
|
+
probes = info.get("live_probes") or []
|
|
311
|
+
dockers = info.get("docker") or []
|
|
312
|
+
lines.append(Text.from_markup(
|
|
313
|
+
f" [cyan]{svc}[/cyan]: {len(probes)} probe(s), docker: "
|
|
314
|
+
+ ", ".join(
|
|
315
|
+
d.get("compose_service", "?") for d in dockers[:4]
|
|
316
|
+
)
|
|
317
|
+
+ ("…" if len(dockers) > 4 else "")
|
|
318
|
+
))
|
|
319
|
+
lines.append(Text.from_markup(
|
|
320
|
+
" [dim]Pełna lista: sekcja monitoring: w wup.yaml (BEGIN WUP MONITORING MANIFEST)[/dim]"
|
|
321
|
+
))
|
|
322
|
+
|
|
277
323
|
# --- visual diff section ---
|
|
278
324
|
if wup_config.visual_diff and wup_config.visual_diff.enabled:
|
|
279
325
|
from .visual_diff import VisualDiffer
|
|
@@ -439,6 +485,91 @@ def map_deps(
|
|
|
439
485
|
console.print(f"[dim]Files: {len(deps.get('files', {}))}[/dim]")
|
|
440
486
|
|
|
441
487
|
|
|
488
|
+
@app.command("sync-testql")
|
|
489
|
+
def sync_testql(
|
|
490
|
+
project: str = typer.Argument(".", help="Path to the project root directory"),
|
|
491
|
+
write: bool = typer.Option(False, "--write", "-w", help="Write monitoring manifest block into wup.yaml"),
|
|
492
|
+
merge_endpoints: bool = typer.Option(
|
|
493
|
+
False,
|
|
494
|
+
"--merge-endpoints",
|
|
495
|
+
help="Also merge discovered paths into testql.endpoints_by_service (rewrites YAML body)",
|
|
496
|
+
),
|
|
497
|
+
config: Optional[str] = typer.Option(None, "--config", "-C", help="Path to wup.yaml config file"),
|
|
498
|
+
):
|
|
499
|
+
"""
|
|
500
|
+
Discover monitoring targets and document them in wup.yaml.
|
|
501
|
+
|
|
502
|
+
With ``--write``, appends/updates the auto-generated ``monitoring:`` block
|
|
503
|
+
(Docker Compose services, live HTTP probes, sources). Use this to verify
|
|
504
|
+
whether a failure is a WUP config gap vs a down container.
|
|
505
|
+
|
|
506
|
+
Use ``--merge-endpoints`` cautiously — it re-serializes wup.yaml (may drop comments).
|
|
507
|
+
"""
|
|
508
|
+
import json
|
|
509
|
+
|
|
510
|
+
from .config import find_config_file, load_config
|
|
511
|
+
from .monitoring_manifest import (
|
|
512
|
+
MANIFEST_BEGIN,
|
|
513
|
+
build_monitoring_manifest,
|
|
514
|
+
format_manifest_summary,
|
|
515
|
+
patch_wup_yaml_monitoring,
|
|
516
|
+
)
|
|
517
|
+
from .testql_monitor import TestQLMonitor
|
|
518
|
+
|
|
519
|
+
project_path = Path(project).resolve()
|
|
520
|
+
if not project_path.exists():
|
|
521
|
+
console.print(f"[red]Error: Project path '{project}' does not exist[/red]")
|
|
522
|
+
raise typer.Exit(1)
|
|
523
|
+
|
|
524
|
+
config_path = Path(config) if config else find_config_file(project_path)
|
|
525
|
+
wup_config = load_config(project_path, config_path)
|
|
526
|
+
monitor = TestQLMonitor(project_path, wup_config)
|
|
527
|
+
suggested = monitor.suggested_endpoints_by_service()
|
|
528
|
+
manifest = build_monitoring_manifest(project_path, wup_config)
|
|
529
|
+
|
|
530
|
+
console.print("[bold]Monitoring manifest (preview):[/bold]")
|
|
531
|
+
console.print(format_manifest_summary(manifest))
|
|
532
|
+
|
|
533
|
+
if suggested:
|
|
534
|
+
console.print()
|
|
535
|
+
console.print("[bold]Suggested testql.endpoints_by_service additions:[/bold]")
|
|
536
|
+
console.print(json.dumps(suggested, indent=2))
|
|
537
|
+
|
|
538
|
+
if not write:
|
|
539
|
+
console.print()
|
|
540
|
+
console.print("[dim]Run: wup sync-testql . --write → dokumentacja w wup.yaml[/dim]")
|
|
541
|
+
return
|
|
542
|
+
|
|
543
|
+
if config_path is None:
|
|
544
|
+
console.print("[red]No wup.yaml found — run `wup init` first[/red]")
|
|
545
|
+
raise typer.Exit(1)
|
|
546
|
+
|
|
547
|
+
if merge_endpoints and suggested:
|
|
548
|
+
import yaml as pyyaml
|
|
549
|
+
|
|
550
|
+
merged = dict(wup_config.testql.endpoints_by_service or {})
|
|
551
|
+
for service, paths in suggested.items():
|
|
552
|
+
existing = set(merged.get(service, []))
|
|
553
|
+
existing.update(paths)
|
|
554
|
+
merged[service] = sorted(existing)
|
|
555
|
+
raw = pyyaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
556
|
+
raw.setdefault("testql", {})["endpoints_by_service"] = merged
|
|
557
|
+
wup_config.testql.endpoints_by_service = merged
|
|
558
|
+
manifest = build_monitoring_manifest(project_path, wup_config)
|
|
559
|
+
body = pyyaml.safe_dump(
|
|
560
|
+
{k: v for k, v in raw.items() if k != "monitoring"},
|
|
561
|
+
sort_keys=False,
|
|
562
|
+
allow_unicode=True,
|
|
563
|
+
default_flow_style=False,
|
|
564
|
+
)
|
|
565
|
+
config_path.write_text(body.rstrip() + "\n\n", encoding="utf-8")
|
|
566
|
+
console.print("[yellow]Merged endpoints_by_service (review git diff for comment loss)[/yellow]")
|
|
567
|
+
|
|
568
|
+
patch_wup_yaml_monitoring(config_path, manifest)
|
|
569
|
+
console.print(f"[green]✓ monitoring manifest written to {config_path}[/green]")
|
|
570
|
+
console.print(f"[dim]Szukaj w pliku: {MANIFEST_BEGIN}[/dim]")
|
|
571
|
+
|
|
572
|
+
|
|
442
573
|
@app.command()
|
|
443
574
|
def assistant(
|
|
444
575
|
quick: bool = typer.Option(False, "--quick", "-q", help="Non-interactive mode with auto-detected values"),
|
|
@@ -176,6 +176,9 @@ def validate_config(raw: dict) -> WupConfig:
|
|
|
176
176
|
output_format=testql_raw.get("output_format", "json"),
|
|
177
177
|
extra_args=testql_raw.get("extra_args", ["--timeout 10s"]),
|
|
178
178
|
endpoint_discovery=testql_raw.get("endpoint_discovery", True),
|
|
179
|
+
probe_interval_s=int(testql_raw.get("probe_interval_s", 0) or 0),
|
|
180
|
+
health_scenario=testql_raw.get("health_scenario", ""),
|
|
181
|
+
service_map_globs=testql_raw.get("service_map_globs", []),
|
|
179
182
|
base_url=testql_raw.get("base_url", ""),
|
|
180
183
|
base_url_env=testql_raw.get("base_url_env", "WUP_BASE_URL"),
|
|
181
184
|
explicit_endpoints=testql_raw.get("explicit_endpoints", []),
|
|
@@ -328,6 +331,9 @@ def save_config(config: WupConfig, output_path: Path):
|
|
|
328
331
|
"output_format": config.testql.output_format,
|
|
329
332
|
"extra_args": config.testql.extra_args,
|
|
330
333
|
"endpoint_discovery": config.testql.endpoint_discovery,
|
|
334
|
+
"probe_interval_s": config.testql.probe_interval_s,
|
|
335
|
+
"health_scenario": config.testql.health_scenario,
|
|
336
|
+
"service_map_globs": config.testql.service_map_globs,
|
|
331
337
|
"base_url": config.testql.base_url,
|
|
332
338
|
"base_url_env": config.testql.base_url_env,
|
|
333
339
|
"explicit_endpoints": config.testql.explicit_endpoints,
|
|
@@ -60,7 +60,10 @@ class TestQLConfig:
|
|
|
60
60
|
smoke_scenario: str = "smoke.testql.toon.yaml"
|
|
61
61
|
output_format: str = "json"
|
|
62
62
|
extra_args: List[str] = field(default_factory=lambda: ["--timeout 10s"])
|
|
63
|
-
endpoint_discovery: bool = True #
|
|
63
|
+
endpoint_discovery: bool = True # Merge health probes from scenarios + service maps
|
|
64
|
+
probe_interval_s: int = 0 # Periodic live probes for all services (0 = file-change only)
|
|
65
|
+
health_scenario: str = "" # Optional TestQL scenario run live (not --dry-run) on each quick pass
|
|
66
|
+
service_map_globs: List[str] = field(default_factory=list) # e.g. testql-testing/service-map/*.yaml
|
|
64
67
|
base_url: str = ""
|
|
65
68
|
base_url_env: str = "WUP_BASE_URL"
|
|
66
69
|
explicit_endpoints: List[str] = field(default_factory=list)
|