wup 0.2.8__tar.gz → 0.2.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wup
3
- Version: 0.2.8
3
+ Version: 0.2.9
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
@@ -28,17 +28,17 @@ Dynamic: license-file
28
28
 
29
29
  ## AI Cost Tracking
30
30
 
31
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.8-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.35-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
31
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.9-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
33
33
 
34
- - 🤖 **LLM usage:** $1.3500 (9 commits)
35
- - 👤 **Human dev:** ~$228 (2.3h @ $100/h, 30min dedup)
34
+ - 🤖 **LLM usage:** $1.5000 (10 commits)
35
+ - 👤 **Human dev:** ~$240 (2.4h @ $100/h, 30min dedup)
36
36
 
37
37
  Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
38
38
 
39
39
  ---
40
40
 
41
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.8-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
41
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.9-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
42
42
 
43
43
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
44
44
 
@@ -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.8-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-$1.35-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.3h-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.9-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-$1.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $1.3500 (9 commits)
10
- - 👤 **Human dev:** ~$228 (2.3h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $1.5000 (10 commits)
10
+ - 👤 **Human dev:** ~$240 (2.4h @ $100/h, 30min dedup)
11
11
 
12
12
  Generated on 2026-04-29 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.8-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.9-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,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wup"
7
- version = "0.2.8"
7
+ version = "0.2.9"
8
8
  description = "WUP (What's Up) - Intelligent file watcher for regression testing in large projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,6 +1,7 @@
1
1
  """End-to-end tests for WUP CLI and workflows."""
2
2
 
3
3
  import json
4
+ import os
4
5
  import subprocess
5
6
  import sys
6
7
  import tempfile
@@ -11,13 +12,29 @@ from typing import List
11
12
  import pytest
12
13
 
13
14
 
15
+ def run_wup_command(args, cwd=None, timeout=30, capture_output=True, text=True):
16
+ """Helper to run WUP commands with PYTHONPATH set."""
17
+ env = os.environ.copy()
18
+ # Add project root to PYTHONPATH so subprocess can find wup module
19
+ project_root = Path(__file__).parent.parent
20
+ env["PYTHONPATH"] = str(project_root) + ":" + env.get("PYTHONPATH", "")
21
+ return subprocess.run(
22
+ args,
23
+ cwd=cwd,
24
+ capture_output=capture_output,
25
+ text=text,
26
+ timeout=timeout,
27
+ env=env
28
+ )
29
+
30
+
14
31
  class TestE2ECLI:
15
32
  """End-to-end tests for CLI commands."""
16
33
 
17
34
  def test_cli_init_creates_config_file(self):
18
35
  """Test that wup init creates a wup.yaml configuration file."""
19
36
  with tempfile.TemporaryDirectory() as tmpdir:
20
- result = subprocess.run(
37
+ result = run_wup_command(
21
38
  [sys.executable, "-m", "wup.cli", "init", "--output", str(Path(tmpdir) / "wup.yaml")],
22
39
  cwd=tmpdir,
23
40
  capture_output=True,
@@ -37,7 +54,7 @@ class TestE2ECLI:
37
54
  def test_cli_init_default_location(self):
38
55
  """Test that wup init creates wup.yaml in current directory by default."""
39
56
  with tempfile.TemporaryDirectory() as tmpdir:
40
- result = subprocess.run(
57
+ result = run_wup_command(
41
58
  [sys.executable, "-m", "wup.cli", "init"],
42
59
  cwd=tmpdir,
43
60
  capture_output=True,
@@ -67,7 +84,7 @@ def get_users():
67
84
  return []
68
85
  """)
69
86
 
70
- result = subprocess.run(
87
+ result = run_wup_command(
71
88
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir, "--framework", "fastapi"],
72
89
  cwd=tmpdir,
73
90
  capture_output=True,
@@ -98,7 +115,7 @@ def get_users():
98
115
  "files": {"app/users/routes.py": ["/users"]}
99
116
  }))
100
117
 
101
- result = subprocess.run(
118
+ result = run_wup_command(
102
119
  [sys.executable, "-m", "wup.cli", "status", "--deps", str(deps_file)],
103
120
  cwd=tmpdir,
104
121
  capture_output=True,
@@ -118,25 +135,23 @@ class TestE2EWorkflow:
118
135
  """Test complete workflow from config to file watching."""
119
136
  with tempfile.TemporaryDirectory() as tmpdir:
120
137
  # Initialize config
121
- subprocess.run(
138
+ run_wup_command(
122
139
  [sys.executable, "-m", "wup.cli", "init"],
123
140
  cwd=tmpdir,
124
- capture_output=True,
125
141
  timeout=10
126
142
  )
127
-
143
+
128
144
  # Create project structure
129
145
  app_dir = Path(tmpdir) / "app" / "users"
130
146
  app_dir.mkdir(parents=True)
131
-
147
+
132
148
  routes_file = app_dir / "routes.py"
133
149
  routes_file.write_text("def handler(): pass\n")
134
-
150
+
135
151
  # Build dependencies
136
- subprocess.run(
152
+ run_wup_command(
137
153
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir, "--framework", "fastapi"],
138
154
  cwd=tmpdir,
139
- capture_output=True,
140
155
  timeout=30
141
156
  )
142
157
 
@@ -186,19 +201,10 @@ test_strategy:
186
201
  routes_file.write_text("def handler(): pass\n")
187
202
 
188
203
  # Build dependencies
189
- import os
190
- env = os.environ.copy()
191
- # Add project root to PYTHONPATH so subprocess can find wup module
192
- project_root = Path(__file__).parent.parent
193
- env["PYTHONPATH"] = str(project_root) + ":" + env.get("PYTHONPATH", "")
194
-
195
- result = subprocess.run(
204
+ result = run_wup_command(
196
205
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir],
197
206
  cwd=tmpdir,
198
- capture_output=True,
199
- text=True,
200
- timeout=30,
201
- env=env
207
+ timeout=30
202
208
  )
203
209
 
204
210
  assert result.returncode == 0
@@ -241,7 +247,7 @@ services:
241
247
  md_file.write_text("# API\n")
242
248
 
243
249
  # Build dependencies
244
- result = subprocess.run(
250
+ result = run_wup_command(
245
251
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir],
246
252
  cwd=tmpdir,
247
253
  capture_output=True,
@@ -312,7 +318,7 @@ def login():
312
318
  """)
313
319
 
314
320
  # Build dependencies for FastAPI
315
- result = subprocess.run(
321
+ result = run_wup_command(
316
322
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir, "--framework", "fastapi"],
317
323
  cwd=tmpdir,
318
324
  capture_output=True,
@@ -338,7 +344,7 @@ class TestE2EErrorHandling:
338
344
  config_file = Path(tmpdir) / "wup.yaml"
339
345
  config_file.write_text("invalid: yaml: content: [")
340
346
 
341
- result = subprocess.run(
347
+ result = run_wup_command(
342
348
  [sys.executable, "-m", "wup.cli", "status"],
343
349
  cwd=tmpdir,
344
350
  capture_output=True,
@@ -351,7 +357,7 @@ class TestE2EErrorHandling:
351
357
 
352
358
  def test_cli_handles_missing_project(self):
353
359
  """Test that CLI handles missing project directory."""
354
- result = subprocess.run(
360
+ result = run_wup_command(
355
361
  [sys.executable, "-m", "wup.cli", "map-deps", "/nonexistent/path"],
356
362
  capture_output=True,
357
363
  text=True,
@@ -364,7 +370,7 @@ class TestE2EErrorHandling:
364
370
  def test_cli_handles_empty_project(self):
365
371
  """Test that CLI handles empty project directory."""
366
372
  with tempfile.TemporaryDirectory() as tmpdir:
367
- result = subprocess.run(
373
+ result = run_wup_command(
368
374
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir],
369
375
  cwd=tmpdir,
370
376
  capture_output=True,
@@ -394,7 +400,7 @@ class TestE2EPerformance:
394
400
  routes_file.write_text("def handler(): pass\n")
395
401
 
396
402
  start_time = time.time()
397
- result = subprocess.run(
403
+ result = run_wup_command(
398
404
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir],
399
405
  cwd=tmpdir,
400
406
  capture_output=True,
@@ -409,7 +415,7 @@ class TestE2EPerformance:
409
415
  """Test init command performance."""
410
416
  with tempfile.TemporaryDirectory() as tmpdir:
411
417
  start_time = time.time()
412
- result = subprocess.run(
418
+ result = run_wup_command(
413
419
  [sys.executable, "-m", "wup.cli", "init"],
414
420
  cwd=tmpdir,
415
421
  capture_output=True,
@@ -461,7 +467,7 @@ services:
461
467
  routes_file.write_text("def handler(): pass\n")
462
468
 
463
469
  # Build dependencies
464
- result = subprocess.run(
470
+ result = run_wup_command(
465
471
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir],
466
472
  cwd=tmpdir,
467
473
  capture_output=True,
@@ -500,7 +506,7 @@ services:
500
506
  routes_file.write_text("def handler(): pass\n")
501
507
 
502
508
  # Build dependencies
503
- result = subprocess.run(
509
+ result = run_wup_command(
504
510
  [sys.executable, "-m", "wup.cli", "map-deps", tmpdir],
505
511
  cwd=tmpdir,
506
512
  capture_output=True,
@@ -17,15 +17,16 @@ def test_process_changed_file_creates_track_on_failure():
17
17
 
18
18
  scenario_dir = root / "testql-scenarios"
19
19
  scenario_dir.mkdir(parents=True, exist_ok=True)
20
- failing_scenario = scenario_dir / "api-users-failing.testql.toon.yaml"
20
+ failing_scenario = scenario_dir / "app-users.testql.toon.yaml"
21
21
  failing_scenario.write_text("name: failing\n", encoding="utf-8")
22
22
 
23
23
  # Pass empty config to prevent loading from temp dir
24
+ from wup.models.config import TestQLConfig
24
25
  empty_config = WupConfig(
25
26
  project=ProjectConfig(name="test"),
26
27
  services=[],
27
28
  test_strategy=None,
28
- testql=None
29
+ testql=TestQLConfig(scenario_dir="testql-scenarios")
29
30
  )
30
31
  watcher = TestQLWatcher(
31
32
  project_root=str(root),
@@ -790,6 +790,47 @@ def test_import():
790
790
  from wup import WupWatcher, DependencyMapper # noqa: F401
791
791
 
792
792
 
793
+ class TestFileFiltering:
794
+ """Tests for file type filtering."""
795
+
796
+ def test_should_watch_file_with_config(self):
797
+ """Test file filtering with configured file types."""
798
+ from wup.models.config import WupConfig, ProjectConfig, WatchConfig
799
+
800
+ with tempfile.TemporaryDirectory() as tmpdir:
801
+ config = WupConfig(
802
+ project=ProjectConfig(name="test"),
803
+ watch=WatchConfig(file_types=[".py", ".ts", ".tsx", ".js"])
804
+ )
805
+ watcher = WupWatcher(tmpdir, config=config)
806
+
807
+ # Should watch allowed types
808
+ assert watcher.should_watch_file(str(Path(tmpdir) / "app.py"))
809
+ assert watcher.should_watch_file(str(Path(tmpdir) / "component.ts"))
810
+ assert watcher.should_watch_file(str(Path(tmpdir) / "app.tsx"))
811
+ assert watcher.should_watch_file(str(Path(tmpdir) / "main.js"))
812
+
813
+ # Should skip disallowed types
814
+ assert not watcher.should_watch_file(str(Path(tmpdir) / "README.md"))
815
+ assert not watcher.should_watch_file(str(Path(tmpdir) / "config.yaml"))
816
+
817
+ def test_should_watch_file_without_config(self):
818
+ """Test file filtering without configured file types (watch all)."""
819
+ from wup.models.config import WupConfig, ProjectConfig, WatchConfig
820
+
821
+ with tempfile.TemporaryDirectory() as tmpdir:
822
+ config = WupConfig(
823
+ project=ProjectConfig(name="test"),
824
+ watch=WatchConfig(file_types=[])
825
+ )
826
+ watcher = WupWatcher(tmpdir, config=config)
827
+
828
+ # Should watch all files when no filter configured
829
+ assert watcher.should_watch_file(str(Path(tmpdir) / "app.py"))
830
+ assert watcher.should_watch_file(str(Path(tmpdir) / "README.md"))
831
+ assert watcher.should_watch_file(str(Path(tmpdir) / "config.yaml"))
832
+
833
+
793
834
  class TestConfigModels:
794
835
  """Tests for configuration dataclasses."""
795
836
 
@@ -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.8"
10
+ __version__ = "0.2.9"
11
11
  __author__ = "Tom Sapletta"
12
12
 
13
13
  from .config import load_config, save_config, get_default_config
@@ -150,7 +150,11 @@ def validate_config(raw: dict) -> WupConfig:
150
150
  smoke_scenario=testql_raw.get("smoke_scenario", "smoke.testql.toon.yaml"),
151
151
  output_format=testql_raw.get("output_format", "json"),
152
152
  extra_args=testql_raw.get("extra_args", ["--timeout 10s"]),
153
- endpoint_discovery=testql_raw.get("endpoint_discovery", True)
153
+ endpoint_discovery=testql_raw.get("endpoint_discovery", True),
154
+ base_url=testql_raw.get("base_url", ""),
155
+ base_url_env=testql_raw.get("base_url_env", "WUP_BASE_URL"),
156
+ explicit_endpoints=testql_raw.get("explicit_endpoints", []),
157
+ endpoints_by_service=testql_raw.get("endpoints_by_service", {})
154
158
  )
155
159
 
156
160
  return WupConfig(
@@ -215,7 +219,12 @@ def save_config(config: WupConfig, output_path: Path):
215
219
  "scenario_dir": config.testql.scenario_dir,
216
220
  "smoke_scenario": config.testql.smoke_scenario,
217
221
  "output_format": config.testql.output_format,
218
- "extra_args": config.testql.extra_args
222
+ "extra_args": config.testql.extra_args,
223
+ "endpoint_discovery": config.testql.endpoint_discovery,
224
+ "base_url": config.testql.base_url,
225
+ "base_url_env": config.testql.base_url_env,
226
+ "explicit_endpoints": config.testql.explicit_endpoints,
227
+ "endpoints_by_service": config.testql.endpoints_by_service,
219
228
  }
220
229
  }
221
230
 
@@ -375,6 +375,26 @@ class WupWatcher:
375
375
  await self.process_test_queue_once()
376
376
  await asyncio.sleep(self.debounce_seconds)
377
377
 
378
+ def should_watch_file(self, file_path: str) -> bool:
379
+ """
380
+ Check if a file should be watched based on configured file types.
381
+
382
+ Args:
383
+ file_path: Path to the file
384
+
385
+ Returns:
386
+ True if file should be watched, False otherwise
387
+ """
388
+ normalized = str(file_path).lower()
389
+ if normalized.endswith(".testql.toon.yaml"):
390
+ return True
391
+
392
+ if not self.config.watch.file_types:
393
+ return True
394
+
395
+ file_suffix = Path(file_path).suffix.lower()
396
+ return file_suffix in self.config.watch.file_types
397
+
378
398
  def on_file_change(self, file_path: str):
379
399
  """
380
400
  Handle file change event.
@@ -382,6 +402,10 @@ class WupWatcher:
382
402
  Args:
383
403
  file_path: Path to the changed file
384
404
  """
405
+ # Check file type filter
406
+ if not self.should_watch_file(file_path):
407
+ return
408
+
385
409
  # Only watch relevant directories
386
410
  rel_path = self._to_relative_path(file_path)
387
411
  parts = rel_path.parts
@@ -59,6 +59,10 @@ class TestQLConfig:
59
59
  output_format: str = "json"
60
60
  extra_args: List[str] = field(default_factory=lambda: ["--timeout 10s"])
61
61
  endpoint_discovery: bool = True # Enable automatic endpoint discovery from scenarios
62
+ base_url: str = ""
63
+ base_url_env: str = "WUP_BASE_URL"
64
+ explicit_endpoints: List[str] = field(default_factory=list)
65
+ endpoints_by_service: Dict[str, List[str]] = field(default_factory=dict)
62
66
 
63
67
 
64
68
  @dataclass
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import json
7
+ import os
7
8
  import re
8
9
  import subprocess
9
10
  import time
@@ -68,16 +69,13 @@ class TestQLWatcher(WupWatcher):
68
69
  # Pass config to parent class
69
70
  super().__init__(project_root=project_root, config=config, **kwargs)
70
71
 
71
- # Use config values if available, otherwise use parameters
72
- if config.testql:
72
+ # Use config scenario_dir if available, otherwise use parameter default
73
+ if config and config.testql and config.testql.scenario_dir:
73
74
  self.scenarios_dir = self.project_root / config.testql.scenario_dir
74
- self.testql_bin = testql_bin # CLI parameter takes precedence
75
- # Use extra_args from config if needed
76
- self.testql_extra_args = config.testql.extra_args
77
75
  else:
78
76
  self.scenarios_dir = self.project_root / scenarios_dir
79
- self.testql_bin = testql_bin
80
- self.testql_extra_args = []
77
+ self.testql_bin = testql_bin
78
+ self.testql_extra_args = config.testql.extra_args if config and config.testql else []
81
79
 
82
80
  self.quick_limit = quick_limit
83
81
  self.track_dir = self.project_root / track_dir
@@ -93,6 +91,41 @@ class TestQLWatcher(WupWatcher):
93
91
  raw_tokens = re.split(r"[^a-zA-Z0-9]+", service.lower())
94
92
  return [token for token in raw_tokens if len(token) >= 3]
95
93
 
94
+ def _get_config_endpoints_for_service(self, service: str) -> List[str]:
95
+ by_service = self.config.testql.endpoints_by_service or {}
96
+ explicit = self.config.testql.explicit_endpoints or []
97
+
98
+ service_specific = by_service.get(service, [])
99
+ merged: List[str] = []
100
+ for endpoint in [*service_specific, *explicit]:
101
+ if endpoint not in merged:
102
+ merged.append(endpoint)
103
+ return merged
104
+
105
+ def _resolve_base_url(self) -> str:
106
+ base_url = (self.config.testql.base_url or "").strip()
107
+ if base_url:
108
+ return base_url.rstrip("/")
109
+
110
+ env_key = (self.config.testql.base_url_env or "WUP_BASE_URL").strip()
111
+ env_url = os.getenv(env_key, "").strip()
112
+ if env_url:
113
+ return env_url.rstrip("/")
114
+
115
+ return ""
116
+
117
+ def _to_full_url(self, endpoint: str) -> str:
118
+ if endpoint.startswith("http://") or endpoint.startswith("https://"):
119
+ return endpoint
120
+
121
+ base_url = self._resolve_base_url()
122
+ if not base_url:
123
+ return endpoint
124
+
125
+ if endpoint.startswith("/"):
126
+ return f"{base_url}{endpoint}"
127
+ return f"{base_url}/{endpoint}"
128
+
96
129
  def _discover_scenarios(self) -> List[Path]:
97
130
  if not self.scenarios_dir.exists():
98
131
  return []
@@ -210,6 +243,11 @@ class TestQLWatcher(WupWatcher):
210
243
  return track_path
211
244
 
212
245
  async def run_quick_test(self, service: str, endpoints: List[str]) -> bool:
246
+ merged_endpoints = list(endpoints)
247
+ for configured_endpoint in self._get_config_endpoints_for_service(service):
248
+ if configured_endpoint not in merged_endpoints:
249
+ merged_endpoints.append(configured_endpoint)
250
+
213
251
  scenarios = self._select_scenarios_for_service(service)
214
252
 
215
253
  # Apply service-specific quick limit
@@ -224,7 +262,7 @@ class TestQLWatcher(WupWatcher):
224
262
  return True
225
263
 
226
264
  self.console.print(
227
- f"[cyan]🧪 Quick TestQL for {service} ({len(scenarios)} scenarios / {len(endpoints)} endpoints)[/cyan]"
265
+ f"[cyan]🧪 Quick TestQL for {service} ({len(scenarios)} scenarios / {len(merged_endpoints)} endpoints)[/cyan]"
228
266
  )
229
267
 
230
268
  for scenario in scenarios:
@@ -255,10 +293,16 @@ class TestQLWatcher(WupWatcher):
255
293
  return True
256
294
 
257
295
  async def run_detail_test(self, service: str, endpoints: List[str]) -> Dict:
296
+ merged_endpoints = list(endpoints)
297
+ for configured_endpoint in self._get_config_endpoints_for_service(service):
298
+ if configured_endpoint not in merged_endpoints:
299
+ merged_endpoints.append(configured_endpoint)
300
+
258
301
  scenarios = self._select_scenarios_for_service(service)
259
302
  results = {
260
303
  "service": service,
261
304
  "total_scenarios": len(scenarios),
305
+ "total_endpoints": len(merged_endpoints),
262
306
  "passed": 0,
263
307
  "failed": 0,
264
308
  "failed_scenarios": [],
@@ -266,7 +310,7 @@ class TestQLWatcher(WupWatcher):
266
310
  }
267
311
 
268
312
  self.console.print(
269
- f"[cyan]🔍 Detail TestQL for {service} ({len(scenarios)} scenarios / {len(endpoints)} endpoints)[/cyan]"
313
+ f"[cyan]🔍 Detail TestQL for {service} ({len(scenarios)} scenarios / {len(merged_endpoints)} endpoints)[/cyan]"
270
314
  )
271
315
 
272
316
  for scenario in scenarios:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wup
3
- Version: 0.2.8
3
+ Version: 0.2.9
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
@@ -28,17 +28,17 @@ Dynamic: license-file
28
28
 
29
29
  ## AI Cost Tracking
30
30
 
31
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.8-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.35-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
31
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.9-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
33
33
 
34
- - 🤖 **LLM usage:** $1.3500 (9 commits)
35
- - 👤 **Human dev:** ~$228 (2.3h @ $100/h, 30min dedup)
34
+ - 🤖 **LLM usage:** $1.5000 (10 commits)
35
+ - 👤 **Human dev:** ~$240 (2.4h @ $100/h, 30min dedup)
36
36
 
37
37
  Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
38
38
 
39
39
  ---
40
40
 
41
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.8-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
41
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.9-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
42
42
 
43
43
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
44
44
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes