diff --git a/agent/cli.py b/agent/cli.py index a862809..da0050c 100644 --- a/agent/cli.py +++ b/agent/cli.py @@ -148,6 +148,208 @@ def add_test(): click.echo(f"\n✅ Test case '{test_id}' successfully added!\n") +# Command: update an existing test case +@cli.command() +@click.argument("test_id") +def update_test(test_id): + """Update an existing test case. Ex: python agent/cli.py update-test TC-001""" + with open(TEST_CASES_PATH, "r") as f: + data = json.load(f) + + test = next((tc for tc in data["test_cases"] if tc["id"] == test_id), None) + + if test is None: + click.echo(f"\n❌ Test case '{test_id}' not found.\n") + return + + click.echo(f"\n✏️ Updating test case '{test_id}' — {test['title']}") + click.echo(" Leave blank to keep the current value.\n") + + title = click.prompt(f" Title", default=test["title"]) + area_path = click.prompt(f" Area", default=test["area_path"]) + assigned_to = click.prompt(f" Assigned to", default=test["assigned_to"]) + + test["title"] = title + test["area_path"] = area_path + test["assigned_to"] = assigned_to + + with open(TEST_CASES_PATH, "w") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + log_event("test_updated", test_id, f"Test case '{test_id}' updated") + + click.echo(f"\n✅ Test case '{test_id}' successfully updated!\n") + +# Command: search test cases +@cli.command() +@click.option("--keyword", "-k", default=None, help="Search by keyword") +@click.option("--assignee", "-a", default=None, help="Search by assignee") +@click.option("--area", "-ar", default=None, help="Search by area") +@click.option("--state", "-s", default=None, help="Search by state") +def search(keyword, assignee, area, state): + """Search test cases by different criteria.""" + with open(TEST_CASES_PATH, "r") as f: + data = json.load(f) + + results = data["test_cases"] + + if keyword: + keyword_lower = keyword.lower() + results = [ + tc for tc in results + if keyword_lower in tc["title"].lower() + or keyword_lower in tc["area_path"].lower() + or keyword_lower in tc["assigned_to"].lower() + ] + + if assignee: + results = [ + tc for tc in results + if assignee.lower() in tc["assigned_to"].lower() + ] + + if area: + results = [ + tc for tc in results + if area.lower() in tc["area_path"].lower() + ] + + if state: + results = [ + tc for tc in results + if state.lower() == tc["state"].lower() + ] + + if not results: + click.echo(f"\n❌ No test cases found.\n") + return + + click.echo(f"\n🔍 Search results:\n") + for tc in results: + click.echo(f" {tc['id']} | {tc['title']} | {tc['state']} | {tc['assigned_to']}") + click.echo("") + + +# Command: show test cases statistics +@cli.command() +def stats(): + """Show test cases statistics.""" + with open(TEST_CASES_PATH, "r") as f: + data = json.load(f) + + total = len(data["test_cases"]) + states = {} + for tc in data["test_cases"]: + state = tc["state"] + states[state] = states.get(state, 0) + 1 + + click.echo(f"\n📊 Test Cases Statistics\n") + click.echo(f" Total: {total}") + for state, count in states.items(): + percentage = round((count / total) * 100) + click.echo(f" {state:<15} {count} ({percentage}%)") + click.echo("") + +# Command: assign a test case to a team member +@cli.command() +@click.argument("test_id") +def assign(test_id): + """Assign a test case to a team member. Ex: python agent/cli.py assign TC-001""" + with open(TEST_CASES_PATH, "r") as f: + data = json.load(f) + + test = next((tc for tc in data["test_cases"] if tc["id"] == test_id), None) + + if test is None: + click.echo(f"\n❌ Test case '{test_id}' not found.\n") + return + + click.echo(f"\n👤 Assigning test case '{test_id}' — {test['title']}") + click.echo(f" Current assignee: {test['assigned_to']}\n") + + new_assignee = click.prompt(" New assignee") + + old_assignee = test["assigned_to"] + test["assigned_to"] = new_assignee + + with open(TEST_CASES_PATH, "w") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + log_event("test_assigned", test_id, f"Assigned from '{old_assignee}' to '{new_assignee}'") + + click.echo(f"\n✅ TC '{test_id}' assigned to '{new_assignee}'!\n") + +# Command: search pull requests +@cli.command() +@click.option("--title", "-t", default=None, help="Search by title") +@click.option("--author", "-a", default=None, help="Search by author") +@click.option("--reviewer", "-r", default=None, help="Search by reviewer") +@click.option("--state", "-s", default="open", help="Search by state (open, closed, merged, all)") +def search_prs(title, author, reviewer, state): + """Search pull requests on GitHub.""" + import subprocess as sp + + click.echo(f"\n🔍 Searching Pull Requests...\n") + + if state == "all": + cmd = ["gh", "pr", "list", "--state", "open", "--json", + "number,title,author,reviewRequests,state,url"] + cmd2 = ["gh", "pr", "list", "--state", "closed", "--json", + "number,title,author,reviewRequests,state,url"] + result2 = sp.run(cmd2, capture_output=True, text=True) + elif state == "merged": + cmd = ["gh", "pr", "list", "--state", "closed", "--json", + "number,title,author,reviewRequests,state,url,mergedAt"] + else: + cmd = ["gh", "pr", "list", "--state", state, "--json", + "number,title,author,reviewRequests,state,url,mergedAt"] + + result = sp.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + click.echo(f"\n❌ Error connecting to GitHub: {result.stderr}\n") + return + + import json as json_module + prs = json_module.loads(result.stdout) + if state == "all" and result2.returncode == 0: + prs += json_module.loads(result2.stdout) + elif state == "merged": + prs = [pr for pr in prs if pr.get("mergedAt")] + elif state == "closed": + prs = [pr for pr in prs if not pr.get("mergedAt")] + + if not prs: + click.echo(f"\n❌ No pull requests found.\n") + return + + if title: + prs = [pr for pr in prs if title.lower() in pr["title"].lower()] + + if author: + prs = [pr for pr in prs if author.lower() in pr["author"]["login"].lower()] + + if reviewer: + prs = [pr for pr in prs + if any(reviewer.lower() in r["login"].lower() + for r in pr.get("reviewRequests", []))] + + if not prs: + click.echo(f"\n❌ No pull requests found with the given filters.\n") + return + + click.echo(f" {'#':<5} {'Title':<45} {'Author':<15} {'State':<10} URL") + click.echo(f" {'─'*5} {'─'*45} {'─'*15} {'─'*10} {'─'*40}") + for pr in prs: + number = f"#{pr['number']}" + title_short = pr["title"][:43] + ".." if len(pr["title"]) > 43 else pr["title"] + author_name = pr["author"]["login"][:13] + pr_state = pr["state"] + url = pr["url"] + click.echo(f" {number:<5} {title_short:<45} {author_name:<15} {pr_state:<10} {url}") + click.echo("") + + # Command: show the team roadmap @cli.command() def roadmap(): diff --git a/agent/menu.py b/agent/menu.py index e765ab6..69d4c4a 100644 --- a/agent/menu.py +++ b/agent/menu.py @@ -11,12 +11,116 @@ def wait_for_menu(): """Waits for the user to press Enter before returning to the menu.""" input("\nPress Enter to return to the menu...") +def test_cases_menu(): + """Submenu for Test Cases.""" + while True: + print("\n📋 Test Cases\n") + + choice = questionary.select( + "What would you like to do?", + choices=[ + "👁️ View Test Cases", + "🔍 Search Test Cases", + "➕ Create Test Case", + "✏️ Update Test Case", + "👤 Assign Test Case", + "📊 Stats", + "📤 Export Test Cases History", + "⬅️ Back" + ] + ).ask() + + if choice == "👁️ View Test Cases": + run_command(["list-tests"]) + wait_for_menu() + + elif choice == "🔍 Search Test Cases": + search_by = questionary.select( + "Search by:", + choices=[ + "🔤 Keyword (searches everything)", + "👤 Assignee", + "📁 Area", + "🔵 State", + ] + ).ask() + + if search_by == "🔤 Keyword (searches everything)": + value = questionary.text("Keyword:").ask() + run_command(["search", "--keyword", value]) + + elif search_by == "👤 Assignee": + value = questionary.text("Assignee name:").ask() + run_command(["search", "--assignee", value]) + + elif search_by == "📁 Area": + value = questionary.text("Area:").ask() + run_command(["search", "--area", value]) + + elif search_by == "🔵 State": + value = questionary.select( + "Select state:", + choices=["Active", "Passed", "Failed", "Blocked", "In Progress"] + ).ask() + run_command(["search", "--state", value]) + + wait_for_menu() + + elif choice == "➕ Create Test Case": + run_command(["add-test"]) + wait_for_menu() + + elif choice == "✏️ Update Test Case": + test_id = questionary.text("Test Case ID (e.g. TC-001):").ask() + run_command(["update-test", test_id]) + wait_for_menu() + + elif choice == "👤 Assign Test Case": + test_id = questionary.text("Test Case ID (e.g. TC-001):").ask() + run_command(["assign", test_id]) + wait_for_menu() + + elif choice == "📊 Stats": + run_command(["stats"]) + wait_for_menu() + + elif choice == "📤 Export Test Cases History": + run_command(["export-tests-history"]) + wait_for_menu() + + elif choice == "⬅️ Back": + break + +def history_menu(): + """Submenu for History.""" + while True: + print("\n📜 History\n") + + choice = questionary.select( + "What would you like to do?", + choices=[ + "📜 View History", + "📤 Export History", + "⬅️ Back" + ] + ).ask() + + if choice == "📜 View History": + run_command(["history"]) + wait_for_menu() + + elif choice == "📤 Export History": + run_command(["export-history"]) + wait_for_menu() + + elif choice == "⬅️ Back": + break + def create_pull_request(): """Interactive flow to create a Pull Request.""" print("\n🔀 Create Pull Request\n") - # Select platform platform = questionary.select( "Select platform:", choices=["GitHub", "GitLab", "Cancel"] @@ -26,13 +130,11 @@ def create_pull_request(): print("\n❌ PR creation cancelled.\n") return - # PR Title title = questionary.text("PR Title:").ask() if not title: print("\n❌ PR creation cancelled.\n") return - # PR Description (pre-filled with template) template_path = ".github/pull_request_template.md" if os.path.exists(template_path): with open(template_path, "r") as f: @@ -55,7 +157,6 @@ def create_pull_request(): print("\n❌ PR creation cancelled.\n") return - # Confirmation confirm = questionary.select( "\nWhat would you like to do?", choices=["✅ Create Pull Request", "❌ Cancel"] @@ -65,7 +166,6 @@ def create_pull_request(): print("\n❌ PR creation cancelled.\n") return - # Check current branch current_branch = subprocess.run( ["git", "branch", "--show-current"], capture_output=True, @@ -77,24 +177,11 @@ def create_pull_request(): print("💡 Tip: Run 'git checkout -b feature/your-feature-name' to create a new branch.\n") return - # Check current branch - current_branch = subprocess.run( - ["git", "branch", "--show-current"], - capture_output=True, - text=True - ).stdout.strip() - - if current_branch == "main": - print("\n⚠️ You are on the 'main' branch. Please switch to a feature branch before creating a PR.") - print("💡 Tip: Run 'git checkout -b feature/your-feature-name' to create a new branch.\n") - return - - # git add, commit, push and create PR print("\n⏳ Preparing your Pull Request...\n") subprocess.run(["git", "add", "."]) subprocess.run(["git", "commit", "-m", title]) - subprocess.run(["git", "push"]) + subprocess.run(["git", "push", "--set-upstream", "origin", current_branch]) if platform == "GitHub": result = subprocess.run( @@ -111,57 +198,122 @@ def create_pull_request(): elif platform == "GitLab": print("\n⚠️ GitLab integration coming soon.\n") -def main(): +def pull_requests_menu(): + """Submenu for Pull Requests.""" while True: - print("\n🤖 QA Agent\n") + print("\n🔀 Pull Requests\n") choice = questionary.select( "What would you like to do?", choices=[ - "📋 View Test Cases", - "➕ Create Test Case", - "🗺️ View Roadmap", - "📜 View History", - "📤 Export History", - "📤 Export Test Cases History", - "🤖 Ask AI", - "🔀 Create Pull Request", - "❌ Exit" + "📋 View All PRs", + "🔍 Search PRs", + "➕ Create Pull Request", + "⬅️ Back" ] ).ask() - if choice == "📋 View Test Cases": - run_command(["list-tests"]) - wait_for_menu() + if choice == "📋 View All PRs": + state = questionary.select( + "Show PRs with state:", + choices=["🟢 Open", "🔴 Closed", "🟣 Merged", "📋 All"] + ).ask() - elif choice == "➕ Create Test Case": - run_command(["add-test"]) - wait_for_menu() + state_map = { + "🟢 Open": "open", + "🔴 Closed": "closed", + "🟣 Merged": "merged", + "📋 All": "all" + } - elif choice == "🗺️ View Roadmap": - run_command(["roadmap"]) + run_command(["search-prs", "--state", state_map[state]]) wait_for_menu() - elif choice == "📜 View History": - run_command(["history"]) + elif choice == "🔍 Search PRs": + search_by = questionary.select( + "Search by:", + choices=[ + "🔤 Title", + "👤 Author", + "👥 Reviewer", + "🔵 State", + "⬅️ Back" + ] + ).ask() + + if search_by == "⬅️ Back": + continue + + if search_by == "🔤 Title": + value = questionary.text("Title (min. 3 characters):").ask() + run_command(["search-prs", "--title", value]) + + elif search_by == "👤 Author": + value = questionary.text("Author username:").ask() + run_command(["search-prs", "--author", value]) + + elif search_by == "👥 Reviewer": + value = questionary.text("Reviewer username:").ask() + run_command(["search-prs", "--reviewer", value]) + + elif search_by == "🔵 State": + value = questionary.select( + "Select state:", + choices=["🟢 Open", "🔴 Closed", "🟣 Merged", "📋 All"] + ).ask() + + state_map = { + "🟢 Open": "open", + "🔴 Closed": "closed", + "🟣 Merged": "merged", + "📋 All": "all" + } + + run_command(["search-prs", "--state", state_map[value]]) + wait_for_menu() - elif choice == "📤 Export History": - run_command(["export-history"]) + elif choice == "➕ Create Pull Request": + create_pull_request() wait_for_menu() - elif choice == "📤 Export Test Cases History": - run_command(["export-tests-history"]) + elif choice == "⬅️ Back": + break + + +def main(): + while True: + print("\n🤖 QA Agent\n") + + choice = questionary.select( + "What would you like to do?", + choices=[ + "📋 Test Cases", + "🗺️ Roadmap", + "📜 History", + "🤖 Ask AI", + "🔀 Pull Requests", + "❌ Exit" + ] + ).ask() + + if choice == "📋 Test Cases": + test_cases_menu() + + elif choice == "🗺️ Roadmap": + run_command(["roadmap"]) wait_for_menu() + elif choice == "📜 History": + history_menu() + elif choice == "🤖 Ask AI": question = questionary.text("What would you like to ask?").ask() run_command(["ask", question]) wait_for_menu() - elif choice == "🔀 Create Pull Request": - create_pull_request() - wait_for_menu() + elif choice == "🔀 Pull Requests": + pull_requests_menu() elif choice == "❌ Exit": print("\n👋 Goodbye!\n") diff --git a/knowledge_base/history.json b/knowledge_base/history.json index e6e176a..8d73bd5 100644 --- a/knowledge_base/history.json +++ b/knowledge_base/history.json @@ -11,6 +11,24 @@ "type": "test_created", "test_id": "TC-005", "details": "Test case 'This is a test' created" + }, + { + "timestamp": "2026-07-15 12:17", + "type": "test_updated", + "test_id": "TC-001", + "details": "Test case 'TC-001' updated" + }, + { + "timestamp": "2026-07-15 14:11", + "type": "test_assigned", + "test_id": "TC-001", + "details": "Assigned from 'Diogo' to 'Tester'" + }, + { + "timestamp": "2026-07-15 14:12", + "type": "test_updated", + "test_id": "TC-002", + "details": "Test case 'TC-002' updated" } ] } \ No newline at end of file diff --git a/knowledge_base/test_cases/test_cases.json b/knowledge_base/test_cases/test_cases.json index dd0fb55..737a5ec 100644 --- a/knowledge_base/test_cases/test_cases.json +++ b/knowledge_base/test_cases/test_cases.json @@ -5,9 +5,9 @@ { "id": "TC-001", "work_item_type": "Test Case", - "title": "Login with valid credentials", - "area_path": "Project/Login", - "assigned_to": "Diogo", + "title": "new title", + "area_path": "Login/logout", + "assigned_to": "Tester", "state": "Active", "steps": [ { diff --git a/knowledge_base/test_cases_history.md b/knowledge_base/test_cases_history.md index 9cdfad7..ff0b72b 100644 --- a/knowledge_base/test_cases_history.md +++ b/knowledge_base/test_cases_history.md @@ -1,8 +1,8 @@ -# Histórico de Test Cases Criados +# Test Cases History -Projeto: QA Agent -Última atualização: 2026-06-26 +Project: QA Agent +Last updated: 2026-06-30 -- TC-001 | Login com credenciais válidas | Passed | Diogo -- TC-002 | Login com credenciais inválidas | Passed | Diogo -- TC-003 Verificar funcionalidades | Verificar funcionalidades | Active | Diogo +- TC-001 | new title | Active | Tester +- TC-002 | Login with invalid credentials | Passed | Diogo +- TC-005 | This is a test | Active | Diogo