Skip to content

Commit 0c1f503

Browse files
authored
Merge branch 'Bugsterapp:main' into main
2 parents 9b3c399 + 6ab9a1c commit 0c1f503

File tree

10 files changed

+159
-125
lines changed

10 files changed

+159
-125
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
strategy:
5656
matrix:
5757
include:
58-
- os: ubuntu-latest
58+
- os: ubuntu-22.04
5959
asset_name: bugster-linux
6060
- os: windows-latest
6161
asset_name: bugster-windows.exe

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to Bugster CLI will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.26]
9+
10+
### Added
11+
- Added `bugster auth` command for setting up or cleaning existing api key
12+
- Added `-- parallel` for `bugster run` command. Accept both `--parallel` and `--max-concurrent`
13+
14+
### Changed
15+
- Changed max test limit to 10
16+
817
## [0.3.25]
918

1019
### Changed

bugster/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
Bugster CLI - A command-line interface tool for managing test cases.
33
"""
44

5-
__version__ = "0.3.25"
5+
__version__ = "0.3.26"

bugster/cli.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,21 @@ def init(
156156
)
157157

158158

159+
@app.command()
160+
def auth(
161+
api_key: Optional[str] = typer.Option(
162+
None, "--api-key", help="Bugster API key to set (starts with 'bugster_')"
163+
),
164+
clear: bool = typer.Option(
165+
False, "--clear", help="Clear the existing API key"
166+
),
167+
):
168+
"""Authenticate with Bugster API key."""
169+
from bugster.commands.auth import auth_command
170+
171+
auth_command(api_key=api_key, clear=clear)
172+
173+
159174
def _run_tests(
160175
path: Optional[str] = typer.Argument(None, help="Path to test file or directory"),
161176
headless: Optional[bool] = typer.Option(
@@ -182,7 +197,7 @@ def _run_tests(
182197
None, "--only-affected", help="Only run tests for affected files or directories"
183198
),
184199
max_concurrent: Optional[int] = typer.Option(
185-
5, "--max-concurrent", help="Maximum number of concurrent tests"
200+
5, "--max-concurrent", "--parallel", help="Maximum number of concurrent tests"
186201
),
187202
verbose: Optional[bool] = typer.Option(False, "--verbose", help="Verbose output"),
188203
):
@@ -384,7 +399,7 @@ def destructive(
384399
),
385400
base_url: Optional[str] = typer.Option(None, help="Override base URL from config"),
386401
max_concurrent: Optional[int] = typer.Option(
387-
None, help="Maximum number of concurrent agents (default: 3)"
402+
None, "--max-concurrent", "--parallel", help="Maximum number of concurrent agents (default: 3)"
388403
),
389404
verbose: bool = typer.Option(False, help="Show detailed agent execution logs"),
390405
run_id: Optional[str] = typer.Option(

bugster/clients/http_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def patch(
4444
endpoint: str,
4545
data: Optional[Dict[str, Any]] = None,
4646
json: Optional[Dict[str, Any]] = None,
47-
**kwargs,
47+
**kwargs,
4848
) -> requests.Response:
4949
"""Make a PATCH request."""
5050
return self._make_request("PATCH", endpoint, data=data, json=json, **kwargs)
@@ -123,4 +123,4 @@ class BugsterHTTPClient(HTTPClient):
123123

124124
def __init__(self):
125125
"""Initialize the HTTP client."""
126-
super().__init__(base_url=libs_settings.bugster_api_url)
126+
super().__init__(base_url=libs_settings.bugster_api_url)

bugster/commands/auth.py

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,81 @@
33
"""
44

55
from rich.console import Console
6-
from rich.prompt import Prompt, Confirm
7-
from bugster.analytics import track_command, BugsterAnalytics
8-
from bugster.utils.user_config import save_api_key
6+
from rich.prompt import Prompt
7+
from bugster.analytics import track_command
8+
from bugster.utils.user_config import save_api_key, get_api_key, load_user_config, save_user_config
99
from bugster.utils.console_messages import AuthMessages
1010
import webbrowser
11+
from typing import Optional
1112

1213
console = Console()
1314

1415
DASHBOARD_URL = "https://gui.bugster.dev/" # Update this with your actual dashboard URL
1516
API_KEY_HINT = "bugster_..."
1617

18+
def clear_api_key():
19+
"""Remove the API key from the config file."""
20+
config = load_user_config()
21+
if "apiKey" in config:
22+
del config["apiKey"]
23+
save_user_config(config)
24+
return True
25+
return False
26+
1727
@track_command("auth")
18-
def auth_command():
28+
def auth_command(api_key: Optional[str] = None, clear: bool = False):
1929
"""Authenticate user with Bugster API key."""
2030
console.print()
2131

32+
# Handle --clear flag
33+
if clear:
34+
existing_key = get_api_key()
35+
if existing_key:
36+
if clear_api_key():
37+
console.print("✅ [green]API key cleared successfully![/green]")
38+
else:
39+
console.print("❌ [red]Error clearing API key.[/red]")
40+
else:
41+
console.print("ℹ️ [yellow]No API key found to clear.[/yellow]")
42+
return
43+
44+
# Clear existing API key before setting new one
45+
existing_key = get_api_key()
46+
if existing_key:
47+
console.print("🔄 [yellow]Removing existing API key...[/yellow]")
48+
clear_api_key()
49+
50+
# Handle --api-key flag
51+
if api_key:
52+
# Validate provided API key
53+
if not api_key.strip():
54+
console.print("❌ [red]API key cannot be empty.[/red]")
55+
return
56+
57+
if not api_key.startswith("bugster_"):
58+
console.print("⚠️ [yellow]Warning: API keys typically start with 'bugster_'[/yellow]")
59+
if Prompt.ask(
60+
"🤔 Continue anyway?",
61+
choices=["y", "n"],
62+
default="n"
63+
) == "n":
64+
console.print("❌ [red]Authentication cancelled.[/red]")
65+
return
66+
67+
console.print("🔄 [yellow]Validating API key...[/yellow]")
68+
69+
if validate_api_key(api_key):
70+
try:
71+
save_api_key(api_key)
72+
console.print("✅ [green]API key saved successfully![/green]")
73+
74+
except Exception as e:
75+
console.print(f"❌ [red]Error saving API key: {str(e)}[/red]")
76+
else:
77+
console.print("❌ [red]Invalid API key. Please check and try again.[/red]")
78+
return
79+
80+
# Interactive flow (original behavior)
2281
# Show authentication panel
2382
auth_panel = AuthMessages.create_auth_panel()
2483
console.print(auth_panel)
@@ -37,13 +96,13 @@ def auth_command():
3796
# Get API key with validation
3897
while True:
3998
AuthMessages.api_key_prompt()
40-
api_key = Prompt.ask(AuthMessages.get_api_key_prompt()).strip()
99+
user_api_key = Prompt.ask(AuthMessages.get_api_key_prompt()).strip()
41100

42-
if not api_key:
101+
if not user_api_key:
43102
AuthMessages.empty_api_key_error()
44103
continue
45104

46-
if not api_key.startswith("bugster_"):
105+
if not user_api_key.startswith("bugster_"):
47106
AuthMessages.invalid_prefix_warning()
48107
if Prompt.ask(
49108
AuthMessages.get_continue_anyway_prompt(),
@@ -54,38 +113,20 @@ def auth_command():
54113

55114
AuthMessages.validating_api_key()
56115

57-
if validate_api_key(api_key):
116+
if validate_api_key(user_api_key):
58117
break
59118
else:
60119
AuthMessages.invalid_api_key_error()
61120
continue
62121

63122
# Save API key
64123
try:
65-
save_api_key(api_key)
124+
save_api_key(user_api_key)
66125
AuthMessages.auth_success()
67126
except Exception as e:
68127
AuthMessages.auth_error(e)
69128
raise
70129

71-
# Analytics opt-in/opt-out prompt (only if not already opted out)
72-
if not BugsterAnalytics.is_opted_out():
73-
analytics_panel = AuthMessages.create_analytics_panel()
74-
console.print(analytics_panel)
75-
console.print()
76-
77-
enable_analytics = Confirm.ask(
78-
f"🤔 Would you like to help improve Bugster by sharing anonymous usage analytics?",
79-
default=True
80-
)
81-
82-
if not enable_analytics:
83-
BugsterAnalytics.create_opt_out_file()
84-
AuthMessages.analytics_disabled()
85-
else:
86-
AuthMessages.analytics_enabled()
87-
console.print()
88-
89130
def validate_api_key(api_key: str) -> bool:
90131
"""Validate API key by making a test request"""
91132
try:

bugster/commands/test.py

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from rich.table import Table
1616

1717
from bugster.analytics import track_command
18-
from bugster.clients.http_client import BugsterHTTPClient
1918
from bugster.clients.mcp_client import MCPStdioClient
2019
from bugster.clients.ws_client import WebSocketClient
2120
from bugster.commands.middleware import require_api_key
@@ -24,7 +23,7 @@
2423
from bugster.libs.services.run_limits_service import (
2524
apply_test_limit,
2625
count_total_tests,
27-
get_test_limit_from_config,
26+
get_test_limit_from_config,
2827
)
2928
from bugster.libs.services.update_service import DetectAffectedSpecsService
3029
from bugster.types import (
@@ -39,14 +38,15 @@
3938
from bugster.utils.console_messages import RunMessages
4039
from bugster.utils.file import (
4140
check_and_update_project_commands,
42-
get_mcp_config_path,
43-
load_always_run_tests,
44-
load_config,
45-
load_test_files,
46-
merge_always_run_with_affected_tests,
41+
get_mcp_config_path,
42+
load_config,
43+
load_test_files,
44+
load_always_run_tests,
45+
merge_always_run_with_affected_tests
4746
)
4847
from bugster.utils.user_config import get_api_key
49-
48+
from bugster.clients.http_client import BugsterHTTPClient
49+
5050
console = Console()
5151
# Color palette for parallel test execution
5252
TEST_COLORS = [
@@ -715,24 +715,18 @@ async def test_command(
715715
try:
716716
affected_tests = DetectAffectedSpecsService().run()
717717
# Merge affected tests with always-run tests
718-
test_files = merge_always_run_with_affected_tests(
719-
affected_tests, always_run_tests
720-
)
718+
test_files = merge_always_run_with_affected_tests(affected_tests, always_run_tests)
721719
except Exception as e:
722720
RunMessages.error(
723721
f"Failed to detect affected specs: {e}. \nRunning all tests..."
724722
)
725723
test_files = load_test_files(path)
726724
# Still merge with always-run tests
727-
test_files = merge_always_run_with_affected_tests(
728-
test_files, always_run_tests
729-
)
725+
test_files = merge_always_run_with_affected_tests(test_files, always_run_tests)
730726
else:
731727
test_files = load_test_files(path)
732728
# Merge all tests with always-run tests
733-
test_files = merge_always_run_with_affected_tests(
734-
test_files, always_run_tests
735-
)
729+
test_files = merge_always_run_with_affected_tests(test_files, always_run_tests)
736730

737731
if not test_files:
738732
RunMessages.no_tests_found()
@@ -741,50 +735,42 @@ async def test_command(
741735
# Separate always-run tests from regular tests
742736
regular_tests = [tf for tf in test_files if not tf.get("always_run", False)]
743737
always_run_tests_list = [tf for tf in test_files if tf.get("always_run", False)]
744-
738+
745739
# Apply limit only to regular tests (not always-run)
746740
original_count = count_total_tests(regular_tests)
747-
limited_regular_tests, folder_distribution = apply_test_limit(
748-
regular_tests, max_tests
749-
)
741+
limited_regular_tests, folder_distribution = apply_test_limit(regular_tests, max_tests)
750742
selected_count = count_total_tests(limited_regular_tests)
751-
743+
752744
# Combine limited regular tests with always-run tests
753745
final_test_files = always_run_tests_list + limited_regular_tests
754746
total_final_count = count_total_tests(final_test_files)
755-
747+
756748
# Print test limit information if limiting was applied
757749
if int(original_count) > int(max_tests):
758750
always_run_count = count_total_tests(always_run_tests_list)
759-
751+
760752
# Calculate folder distribution for always-run tests
761753
always_run_distribution = {}
762754
for test_file in always_run_tests_list:
763755
folder = test_file["file"].parent.name
764-
always_run_distribution[folder] = always_run_distribution.get(
765-
folder, 0
766-
) + len(test_file["content"])
767-
756+
always_run_distribution[folder] = always_run_distribution.get(folder, 0) + len(test_file["content"])
757+
768758
console.print(
769759
RunMessages.create_test_limit_panel(
770760
original_count=original_count,
771761
selected_count=selected_count,
772762
max_tests=max_tests,
773763
folder_distribution=folder_distribution,
774764
always_run_count=always_run_count,
775-
always_run_distribution=always_run_distribution,
765+
always_run_distribution=always_run_distribution
776766
)
777767
)
778-
768+
779769
# Show always-run information
780770
if always_run_tests_list:
781771
always_run_count = count_total_tests(always_run_tests_list)
782-
console.print(
783-
f"[dim]Always-run tests: {always_run_count} (additional to limit)[/dim]"
784-
)
785-
console.print(
786-
f"[dim]Total tests to run: {total_final_count} (regular: {selected_count} + always-run: {always_run_count})[/dim]"
787-
)
772+
console.print(f"[dim]Always-run tests: {always_run_count} (additional to limit)[/dim]")
773+
console.print(f"[dim]Total tests to run: {total_final_count} (regular: {selected_count} + always-run: {always_run_count})[/dim]")
788774

789775
# Use the final combined test files for execution
790776
test_files = final_test_files

bugster/libs/services/run_limits_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,6 @@ def get_test_limit_from_config() -> Optional[int]:
158158
Returns:
159159
Maximum number of tests to run, or None if no limit
160160
"""
161-
return 5
161+
return 10
162162

163163

0 commit comments

Comments
 (0)