feat!: full-access Gitea MCP server (1.0.0): full API coverage, stdio + streamable HTTP, admin and per-user OAuth #71
No files matched your search
@@ -0,0 +1,41 @@
|
||||
"""Static fixtures for the integration suite: throwaway OpenPGP public keys.
|
||||
|
||||
Both keys were generated offline for a fake identity (``Aegis IT Fixture N`` with an
|
||||
``example.test`` address); only the public halves are committed. Gitea accepts a GPG key
|
||||
only when one of its identities carries an activated email address of the account, so the
|
||||
tests first add the matching address to the account (``GPG_EMAILS``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
GPG_EMAILS = (
|
||||
"aegis-it-fixture-1@example.test",
|
||||
"aegis-it-fixture-2@example.test",
|
||||
)
|
||||
|
||||
GPG_PUBLIC_KEYS = (
|
||||
"""\
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mDMEas+oDBYJKwYBBAHaRw8BAQdA7yjFLeF4uUtwBS9tSHRfz1p3CO3zX5G01SqY
|
||||
Kq4Ebuu0NEFlZ2lzIElUIEZpeHR1cmUgMSA8YWVnaXMtaXQtZml4dHVyZS0xQGV4
|
||||
YW1wbGUudGVzdD6IkwQTFgoAOxYhBLEDZkoa8oLNuNJlafBqtfhHw71yBQJqz6gM
|
||||
AhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEPBqtfhHw71yXN8BAPZ7
|
||||
ShczPrCQqX+YN/YP5gU+Cnfnopt3yo66chupK6ADAQD3fdC2kwi8DQQGPdeLb7qC
|
||||
TPcxXQWSnXeVTr3EQKFFAQ==
|
||||
=KK0/
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
""",
|
||||
"""\
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mDMEas+oDBYJKwYBBAHaRw8BAQdAfUIIJtFAtjCsr39V+TnKqtUrWINy+9KsJdgq
|
||||
4CWgjnS0NEFlZ2lzIElUIEZpeHR1cmUgMiA8YWVnaXMtaXQtZml4dHVyZS0yQGV4
|
||||
YW1wbGUudGVzdD6IkwQTFgoAOxYhBFk8Jp2uNta24tbmPees4IAGb9G9BQJqz6gM
|
||||
AhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEOes4IAGb9G9880BAJwB
|
||||
Kl2G944vYVF2XXS3S/PspgOj9Gcm6Q8T/07MPLTIAP0TUTvS2gg2CF3GXp7YCgUf
|
||||
0hZziR0B882mz4IWtfroAQ==
|
||||
=teCc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
""",
|
||||
)
|
||||
@@ -0,0 +1,543 @@
|
||||
{
|
||||
"gitea": "28.1.0",
|
||||
"comment": "Expected outcome class per tool in the sweep: 2xx, or the exact status of a documented non-2xx or allowance. Regenerate with AEGIS_IT_WRITE_BASELINE=1.",
|
||||
"tools": {
|
||||
"gitea_actions_approve_workflow_run": 409,
|
||||
"gitea_actions_cancel_workflow_run": "2xx",
|
||||
"gitea_actions_create_org_variable": "2xx",
|
||||
"gitea_actions_create_repo_variable": "2xx",
|
||||
"gitea_actions_create_user_variable": "2xx",
|
||||
"gitea_actions_delete_action_run": 400,
|
||||
"gitea_actions_delete_artifact": 404,
|
||||
"gitea_actions_delete_org_runner": 404,
|
||||
"gitea_actions_delete_org_secret": "2xx",
|
||||
"gitea_actions_delete_org_variable": "2xx",
|
||||
"gitea_actions_delete_repo_runner": 404,
|
||||
"gitea_actions_delete_repo_secret": "2xx",
|
||||
"gitea_actions_delete_repo_variable": "2xx",
|
||||
"gitea_actions_delete_user_runner": 404,
|
||||
"gitea_actions_delete_user_secret": "2xx",
|
||||
"gitea_actions_delete_user_variable": "2xx",
|
||||
"gitea_actions_disable_workflow": "2xx",
|
||||
"gitea_actions_dispatch_workflow": "2xx",
|
||||
"gitea_actions_download_artifact": 404,
|
||||
"gitea_actions_download_run_job_logs": 404,
|
||||
"gitea_actions_enable_workflow": "2xx",
|
||||
"gitea_actions_force_cancel_workflow_run": 409,
|
||||
"gitea_actions_get_artifact": 404,
|
||||
"gitea_actions_get_artifacts": "2xx",
|
||||
"gitea_actions_get_artifacts_of_run": "2xx",
|
||||
"gitea_actions_get_org_runner": 404,
|
||||
"gitea_actions_get_org_runners": "2xx",
|
||||
"gitea_actions_get_org_variable": "2xx",
|
||||
"gitea_actions_get_org_variables_list": "2xx",
|
||||
"gitea_actions_get_org_workflow_jobs": "2xx",
|
||||
"gitea_actions_get_org_workflow_runs": "2xx",
|
||||
"gitea_actions_get_repo_runner": 404,
|
||||
"gitea_actions_get_repo_runners": "2xx",
|
||||
"gitea_actions_get_repo_variable": "2xx",
|
||||
"gitea_actions_get_repo_variables_list": "2xx",
|
||||
"gitea_actions_get_user_runner": 404,
|
||||
"gitea_actions_get_user_runners": "2xx",
|
||||
"gitea_actions_get_user_variable": "2xx",
|
||||
"gitea_actions_get_user_variables_list": "2xx",
|
||||
"gitea_actions_get_user_workflow_jobs": "2xx",
|
||||
"gitea_actions_get_user_workflow_runs": "2xx",
|
||||
"gitea_actions_get_workflow": "2xx",
|
||||
"gitea_actions_get_workflow_job": "2xx",
|
||||
"gitea_actions_get_workflow_run": "2xx",
|
||||
"gitea_actions_get_workflow_run_attempt": "2xx",
|
||||
"gitea_actions_get_workflow_run_logs": 404,
|
||||
"gitea_actions_get_workflow_runs": "2xx",
|
||||
"gitea_actions_list_action_tasks": "2xx",
|
||||
"gitea_actions_list_repository_workflows": "2xx",
|
||||
"gitea_actions_list_workflow_jobs": "2xx",
|
||||
"gitea_actions_list_workflow_run_attempt_jobs": "2xx",
|
||||
"gitea_actions_list_workflow_run_jobs": "2xx",
|
||||
"gitea_actions_list_workflow_runs": "2xx",
|
||||
"gitea_actions_org_create_runner_registration_token": "2xx",
|
||||
"gitea_actions_org_list_secrets": "2xx",
|
||||
"gitea_actions_repo_create_runner_registration_token": "2xx",
|
||||
"gitea_actions_repo_list_secrets": "2xx",
|
||||
"gitea_actions_rerun_failed_workflow_run": "2xx",
|
||||
"gitea_actions_rerun_workflow_job": 404,
|
||||
"gitea_actions_rerun_workflow_run": 400,
|
||||
"gitea_actions_update_org_runner": 404,
|
||||
"gitea_actions_update_org_secret": "2xx",
|
||||
"gitea_actions_update_org_variable": "2xx",
|
||||
"gitea_actions_update_repo_runner": 404,
|
||||
"gitea_actions_update_repo_secret": "2xx",
|
||||
"gitea_actions_update_repo_variable": "2xx",
|
||||
"gitea_actions_update_user_runner": 404,
|
||||
"gitea_actions_update_user_secret": "2xx",
|
||||
"gitea_actions_update_user_variable": "2xx",
|
||||
"gitea_actions_user_create_runner_registration_token": "2xx",
|
||||
"gitea_admin_add_user_badges": 500,
|
||||
"gitea_admin_adopt_repository": 404,
|
||||
"gitea_admin_create_hook": "2xx",
|
||||
"gitea_admin_create_org": "2xx",
|
||||
"gitea_admin_create_public_key": "2xx",
|
||||
"gitea_admin_create_repo": "2xx",
|
||||
"gitea_admin_create_runner_registration_token": "2xx",
|
||||
"gitea_admin_create_user": "2xx",
|
||||
"gitea_admin_cron_list": "2xx",
|
||||
"gitea_admin_cron_run": "2xx",
|
||||
"gitea_admin_delete_hook": "2xx",
|
||||
"gitea_admin_delete_runner": 404,
|
||||
"gitea_admin_delete_unadopted_repository": 404,
|
||||
"gitea_admin_delete_user": "2xx",
|
||||
"gitea_admin_delete_user_badges": "2xx",
|
||||
"gitea_admin_delete_user_public_key": "2xx",
|
||||
"gitea_admin_edit_hook": "2xx",
|
||||
"gitea_admin_edit_user": "2xx",
|
||||
"gitea_admin_get_all_emails": "2xx",
|
||||
"gitea_admin_get_all_orgs": "2xx",
|
||||
"gitea_admin_get_hook": "2xx",
|
||||
"gitea_admin_get_runner": 404,
|
||||
"gitea_admin_get_runners": "2xx",
|
||||
"gitea_admin_list_hooks": "2xx",
|
||||
"gitea_admin_list_packages": "2xx",
|
||||
"gitea_admin_list_user_badges": "2xx",
|
||||
"gitea_admin_list_workflow_jobs": "2xx",
|
||||
"gitea_admin_list_workflow_runs": "2xx",
|
||||
"gitea_admin_rename_user": "2xx",
|
||||
"gitea_admin_search_emails": "2xx",
|
||||
"gitea_admin_search_users": "2xx",
|
||||
"gitea_admin_unadopted_list": "2xx",
|
||||
"gitea_admin_update_runner": 404,
|
||||
"gitea_api_request": "2xx",
|
||||
"gitea_hook_org_create_hook": "2xx",
|
||||
"gitea_hook_org_delete_hook": "2xx",
|
||||
"gitea_hook_org_edit_hook": "2xx",
|
||||
"gitea_hook_org_get_hook": "2xx",
|
||||
"gitea_hook_org_list_hooks": "2xx",
|
||||
"gitea_hook_repo_create_hook": "2xx",
|
||||
"gitea_hook_repo_delete_git_hook": "2xx",
|
||||
"gitea_hook_repo_delete_hook": "2xx",
|
||||
"gitea_hook_repo_edit_git_hook": "2xx",
|
||||
"gitea_hook_repo_edit_hook": "2xx",
|
||||
"gitea_hook_repo_get_git_hook": "2xx",
|
||||
"gitea_hook_repo_get_hook": "2xx",
|
||||
"gitea_hook_repo_list_git_hooks": "2xx",
|
||||
"gitea_hook_repo_list_hooks": "2xx",
|
||||
"gitea_hook_repo_test_hook": "2xx",
|
||||
"gitea_hook_user_create_hook": "2xx",
|
||||
"gitea_hook_user_delete_hook": "2xx",
|
||||
"gitea_hook_user_edit_hook": "2xx",
|
||||
"gitea_hook_user_get_hook": "2xx",
|
||||
"gitea_hook_user_list_hooks": "2xx",
|
||||
"gitea_issue_add_assignees": "2xx",
|
||||
"gitea_issue_add_label": "2xx",
|
||||
"gitea_issue_add_subscription": "2xx",
|
||||
"gitea_issue_add_time": "2xx",
|
||||
"gitea_issue_check_assignee": "2xx",
|
||||
"gitea_issue_check_subscription": "2xx",
|
||||
"gitea_issue_clear_labels": "2xx",
|
||||
"gitea_issue_create_comment": "2xx",
|
||||
"gitea_issue_create_issue": "2xx",
|
||||
"gitea_issue_create_issue_attachment": "2xx",
|
||||
"gitea_issue_create_issue_blocking": "2xx",
|
||||
"gitea_issue_create_issue_comment_attachment": "2xx",
|
||||
"gitea_issue_create_issue_dependencies": "2xx",
|
||||
"gitea_issue_create_label": "2xx",
|
||||
"gitea_issue_create_milestone": "2xx",
|
||||
"gitea_issue_delete": "2xx",
|
||||
"gitea_issue_delete_comment": "2xx",
|
||||
"gitea_issue_delete_comment_deprecated": "2xx",
|
||||
"gitea_issue_delete_comment_reaction": "2xx",
|
||||
"gitea_issue_delete_issue_attachment": "2xx",
|
||||
"gitea_issue_delete_issue_comment_attachment": "2xx",
|
||||
"gitea_issue_delete_issue_reaction": "2xx",
|
||||
"gitea_issue_delete_label": "2xx",
|
||||
"gitea_issue_delete_milestone": "2xx",
|
||||
"gitea_issue_delete_stopwatch": "2xx",
|
||||
"gitea_issue_delete_subscription": "2xx",
|
||||
"gitea_issue_delete_time": "2xx",
|
||||
"gitea_issue_edit_comment": "2xx",
|
||||
"gitea_issue_edit_comment_deprecated": "2xx",
|
||||
"gitea_issue_edit_issue": "2xx",
|
||||
"gitea_issue_edit_issue_attachment": "2xx",
|
||||
"gitea_issue_edit_issue_comment_attachment": "2xx",
|
||||
"gitea_issue_edit_issue_deadline": "2xx",
|
||||
"gitea_issue_edit_label": "2xx",
|
||||
"gitea_issue_edit_milestone": "2xx",
|
||||
"gitea_issue_get_comment": "2xx",
|
||||
"gitea_issue_get_comment_reactions": "2xx",
|
||||
"gitea_issue_get_comments": "2xx",
|
||||
"gitea_issue_get_comments_and_timeline": "2xx",
|
||||
"gitea_issue_get_issue": "2xx",
|
||||
"gitea_issue_get_issue_attachment": "2xx",
|
||||
"gitea_issue_get_issue_comment_attachment": "2xx",
|
||||
"gitea_issue_get_issue_reactions": "2xx",
|
||||
"gitea_issue_get_label": "2xx",
|
||||
"gitea_issue_get_labels": "2xx",
|
||||
"gitea_issue_get_milestone": "2xx",
|
||||
"gitea_issue_get_milestones_list": "2xx",
|
||||
"gitea_issue_get_repo_comments": "2xx",
|
||||
"gitea_issue_list_blocks": "2xx",
|
||||
"gitea_issue_list_issue_attachments": "2xx",
|
||||
"gitea_issue_list_issue_comment_attachments": "2xx",
|
||||
"gitea_issue_list_issue_dependencies": "2xx",
|
||||
"gitea_issue_list_issues": "2xx",
|
||||
"gitea_issue_list_labels": "2xx",
|
||||
"gitea_issue_list_pinned_issues": "2xx",
|
||||
"gitea_issue_lock_issue": "2xx",
|
||||
"gitea_issue_move_issue_pin": "2xx",
|
||||
"gitea_issue_pin_issue": "2xx",
|
||||
"gitea_issue_post_comment_reaction": "2xx",
|
||||
"gitea_issue_post_issue_reaction": "2xx",
|
||||
"gitea_issue_remove_assignees": "2xx",
|
||||
"gitea_issue_remove_issue_blocking": "2xx",
|
||||
"gitea_issue_remove_issue_dependencies": "2xx",
|
||||
"gitea_issue_remove_label": "2xx",
|
||||
"gitea_issue_replace_labels": "2xx",
|
||||
"gitea_issue_reset_time": "2xx",
|
||||
"gitea_issue_search_issues": "2xx",
|
||||
"gitea_issue_start_stopwatch": "2xx",
|
||||
"gitea_issue_stop_stopwatch": "2xx",
|
||||
"gitea_issue_subscriptions": "2xx",
|
||||
"gitea_issue_tracked_times": "2xx",
|
||||
"gitea_issue_unlock_issue": "2xx",
|
||||
"gitea_issue_unpin_issue": "2xx",
|
||||
"gitea_misc_get_current_token": "2xx",
|
||||
"gitea_misc_get_general_api_settings": "2xx",
|
||||
"gitea_misc_get_general_attachment_settings": "2xx",
|
||||
"gitea_misc_get_general_repository_settings": "2xx",
|
||||
"gitea_misc_get_general_ui_settings": "2xx",
|
||||
"gitea_misc_get_gitignore_template_info": "2xx",
|
||||
"gitea_misc_get_label_template_info": "2xx",
|
||||
"gitea_misc_get_license_template_info": "2xx",
|
||||
"gitea_misc_get_signing_key": 404,
|
||||
"gitea_misc_get_signing_key_ssh": 404,
|
||||
"gitea_misc_get_version": "2xx",
|
||||
"gitea_misc_list_gitignores_templates": "2xx",
|
||||
"gitea_misc_list_label_templates": "2xx",
|
||||
"gitea_misc_list_license_templates": "2xx",
|
||||
"gitea_misc_render_markdown": "2xx",
|
||||
"gitea_misc_render_markdown_raw": "2xx",
|
||||
"gitea_misc_render_markup": "2xx",
|
||||
"gitea_misc_topic_search": "2xx",
|
||||
"gitea_notification_get_list": "2xx",
|
||||
"gitea_notification_get_repo_list": "2xx",
|
||||
"gitea_notification_get_thread": "2xx",
|
||||
"gitea_notification_new_available": "2xx",
|
||||
"gitea_notification_read_list": "2xx",
|
||||
"gitea_notification_read_repo_list": "2xx",
|
||||
"gitea_notification_read_thread": "2xx",
|
||||
"gitea_org_add_issue_to_project_column": "2xx",
|
||||
"gitea_org_block_user": "2xx",
|
||||
"gitea_org_check_user_block": "2xx",
|
||||
"gitea_org_conceal_member": "2xx",
|
||||
"gitea_org_create": "2xx",
|
||||
"gitea_org_create_label": "2xx",
|
||||
"gitea_org_create_org_repo": "2xx",
|
||||
"gitea_org_create_org_repo_deprecated": "2xx",
|
||||
"gitea_org_create_project": "2xx",
|
||||
"gitea_org_create_project_column": "2xx",
|
||||
"gitea_org_delete": "2xx",
|
||||
"gitea_org_delete_avatar": "2xx",
|
||||
"gitea_org_delete_label": "2xx",
|
||||
"gitea_org_delete_member": "2xx",
|
||||
"gitea_org_delete_project": "2xx",
|
||||
"gitea_org_delete_project_column": "2xx",
|
||||
"gitea_org_delete_repos": "2xx",
|
||||
"gitea_org_edit": "2xx",
|
||||
"gitea_org_edit_label": "2xx",
|
||||
"gitea_org_edit_project": "2xx",
|
||||
"gitea_org_edit_project_column": "2xx",
|
||||
"gitea_org_get": "2xx",
|
||||
"gitea_org_get_all": "2xx",
|
||||
"gitea_org_get_label": "2xx",
|
||||
"gitea_org_get_project": "2xx",
|
||||
"gitea_org_get_project_column": "2xx",
|
||||
"gitea_org_is_member": "2xx",
|
||||
"gitea_org_is_public_member": "2xx",
|
||||
"gitea_org_list_activity_feeds": "2xx",
|
||||
"gitea_org_list_blocks": "2xx",
|
||||
"gitea_org_list_labels": "2xx",
|
||||
"gitea_org_list_members": "2xx",
|
||||
"gitea_org_list_project_column_issues": "2xx",
|
||||
"gitea_org_list_project_columns": "2xx",
|
||||
"gitea_org_list_projects": "2xx",
|
||||
"gitea_org_list_public_members": "2xx",
|
||||
"gitea_org_list_repos": "2xx",
|
||||
"gitea_org_move_project_columns": "2xx",
|
||||
"gitea_org_move_project_issue": "2xx",
|
||||
"gitea_org_publicize_member": "2xx",
|
||||
"gitea_org_remove_issue_from_project_column": "2xx",
|
||||
"gitea_org_rename": "2xx",
|
||||
"gitea_org_set_default_project_column": "2xx",
|
||||
"gitea_org_unblock_user": "2xx",
|
||||
"gitea_org_update_avatar": "2xx",
|
||||
"gitea_package_delete_package": "2xx",
|
||||
"gitea_package_delete_package_version": "2xx",
|
||||
"gitea_package_get_latest_package_version": "2xx",
|
||||
"gitea_package_get_package": "2xx",
|
||||
"gitea_package_link_package": "2xx",
|
||||
"gitea_package_list_package_files": "2xx",
|
||||
"gitea_package_list_package_versions": "2xx",
|
||||
"gitea_package_list_packages": "2xx",
|
||||
"gitea_package_unlink_package": "2xx",
|
||||
"gitea_pull_cancel_scheduled_auto_merge": 404,
|
||||
"gitea_pull_create_pull_request": "2xx",
|
||||
"gitea_pull_create_pull_review": "2xx",
|
||||
"gitea_pull_create_pull_review_comment_reply": "2xx",
|
||||
"gitea_pull_create_pull_review_requests": "2xx",
|
||||
"gitea_pull_delete_pull_review": "2xx",
|
||||
"gitea_pull_delete_pull_review_requests": "2xx",
|
||||
"gitea_pull_dismiss_pull_review": "2xx",
|
||||
"gitea_pull_download_pull_diff_or_patch": "2xx",
|
||||
"gitea_pull_edit_pull_request": "2xx",
|
||||
"gitea_pull_get_pull_request": "2xx",
|
||||
"gitea_pull_get_pull_request_by_base_head": "2xx",
|
||||
"gitea_pull_get_pull_request_commits": "2xx",
|
||||
"gitea_pull_get_pull_request_files": "2xx",
|
||||
"gitea_pull_get_pull_review": "2xx",
|
||||
"gitea_pull_get_pull_review_comments": "2xx",
|
||||
"gitea_pull_list_pinned_pull_requests": "2xx",
|
||||
"gitea_pull_list_pull_requests": "2xx",
|
||||
"gitea_pull_list_pull_reviews": "2xx",
|
||||
"gitea_pull_merge_pull_request": "2xx",
|
||||
"gitea_pull_pull_request_is_merged": 404,
|
||||
"gitea_pull_resolve_pull_review_comment": "2xx",
|
||||
"gitea_pull_submit_pull_review": "2xx",
|
||||
"gitea_pull_undismiss_pull_review": "2xx",
|
||||
"gitea_pull_unresolve_pull_review_comment": "2xx",
|
||||
"gitea_pull_update_pull_request": "2xx",
|
||||
"gitea_release_create_release": "2xx",
|
||||
"gitea_release_create_release_attachment": "2xx",
|
||||
"gitea_release_delete_release": "2xx",
|
||||
"gitea_release_delete_release_attachment": "2xx",
|
||||
"gitea_release_delete_release_by_tag": "2xx",
|
||||
"gitea_release_edit_release": "2xx",
|
||||
"gitea_release_edit_release_attachment": "2xx",
|
||||
"gitea_release_get_latest_release": "2xx",
|
||||
"gitea_release_get_release": "2xx",
|
||||
"gitea_release_get_release_attachment": "2xx",
|
||||
"gitea_release_get_release_by_tag": "2xx",
|
||||
"gitea_release_list_release_attachments": "2xx",
|
||||
"gitea_release_list_releases": "2xx",
|
||||
"gitea_repo_accept_repo_transfer": 404,
|
||||
"gitea_repo_add_collaborator": "2xx",
|
||||
"gitea_repo_add_issue_to_project_column": "2xx",
|
||||
"gitea_repo_add_push_mirror": "2xx",
|
||||
"gitea_repo_add_team": "2xx",
|
||||
"gitea_repo_add_topic": "2xx",
|
||||
"gitea_repo_apply_diff_patch": "2xx",
|
||||
"gitea_repo_change_files": "2xx",
|
||||
"gitea_repo_check_assignee": "2xx",
|
||||
"gitea_repo_check_collaborator": "2xx",
|
||||
"gitea_repo_check_subscription": "2xx",
|
||||
"gitea_repo_check_team": "2xx",
|
||||
"gitea_repo_compare_diff": "2xx",
|
||||
"gitea_repo_create_branch": "2xx",
|
||||
"gitea_repo_create_branch_protection": "2xx",
|
||||
"gitea_repo_create_deploy_token": "2xx",
|
||||
"gitea_repo_create_file": "2xx",
|
||||
"gitea_repo_create_fork": "2xx",
|
||||
"gitea_repo_create_key": "2xx",
|
||||
"gitea_repo_create_project": "2xx",
|
||||
"gitea_repo_create_project_column": "2xx",
|
||||
"gitea_repo_create_status": "2xx",
|
||||
"gitea_repo_create_tag": "2xx",
|
||||
"gitea_repo_create_tag_protection": "2xx",
|
||||
"gitea_repo_delete": "2xx",
|
||||
"gitea_repo_delete_avatar": "2xx",
|
||||
"gitea_repo_delete_branch": "2xx",
|
||||
"gitea_repo_delete_branch_protection": "2xx",
|
||||
"gitea_repo_delete_collaborator": "2xx",
|
||||
"gitea_repo_delete_file": "2xx",
|
||||
"gitea_repo_delete_key": "2xx",
|
||||
"gitea_repo_delete_project": "2xx",
|
||||
"gitea_repo_delete_project_column": "2xx",
|
||||
"gitea_repo_delete_push_mirror": "2xx",
|
||||
"gitea_repo_delete_subscription": "2xx",
|
||||
"gitea_repo_delete_tag": "2xx",
|
||||
"gitea_repo_delete_tag_protection": "2xx",
|
||||
"gitea_repo_delete_team": "2xx",
|
||||
"gitea_repo_delete_topic": "2xx",
|
||||
"gitea_repo_download_commit_diff_or_patch": "2xx",
|
||||
"gitea_repo_edit": "2xx",
|
||||
"gitea_repo_edit_branch_protection": "2xx",
|
||||
"gitea_repo_edit_project": "2xx",
|
||||
"gitea_repo_edit_project_column": "2xx",
|
||||
"gitea_repo_edit_tag_protection": "2xx",
|
||||
"gitea_repo_generate_repo": "2xx",
|
||||
"gitea_repo_get": "2xx",
|
||||
"gitea_repo_get_all_commits": "2xx",
|
||||
"gitea_repo_get_annotated_tag": "2xx",
|
||||
"gitea_repo_get_archive": "2xx",
|
||||
"gitea_repo_get_assignees": "2xx",
|
||||
"gitea_repo_get_blob": "2xx",
|
||||
"gitea_repo_get_branch": "2xx",
|
||||
"gitea_repo_get_branch_protection": "2xx",
|
||||
"gitea_repo_get_by_id": "2xx",
|
||||
"gitea_repo_get_combined_status_by_ref": "2xx",
|
||||
"gitea_repo_get_commit_pull_request": 404,
|
||||
"gitea_repo_get_contents": "2xx",
|
||||
"gitea_repo_get_contents_ext": "2xx",
|
||||
"gitea_repo_get_contents_list": "2xx",
|
||||
"gitea_repo_get_editor_config": "2xx",
|
||||
"gitea_repo_get_file_contents": "2xx",
|
||||
"gitea_repo_get_file_contents_post": "2xx",
|
||||
"gitea_repo_get_issue_config": "2xx",
|
||||
"gitea_repo_get_issue_templates": "2xx",
|
||||
"gitea_repo_get_key": "2xx",
|
||||
"gitea_repo_get_languages": "2xx",
|
||||
"gitea_repo_get_licenses": "2xx",
|
||||
"gitea_repo_get_note": 404,
|
||||
"gitea_repo_get_project": "2xx",
|
||||
"gitea_repo_get_project_column": "2xx",
|
||||
"gitea_repo_get_push_mirror_by_remote_name": "2xx",
|
||||
"gitea_repo_get_raw_file": "2xx",
|
||||
"gitea_repo_get_raw_file_or_lfs": "2xx",
|
||||
"gitea_repo_get_repo_permissions": "2xx",
|
||||
"gitea_repo_get_reviewers": "2xx",
|
||||
"gitea_repo_get_single_commit": "2xx",
|
||||
"gitea_repo_get_tag": "2xx",
|
||||
"gitea_repo_get_tag_protection": "2xx",
|
||||
"gitea_repo_get_tree": "2xx",
|
||||
"gitea_repo_list_activity_feeds": "2xx",
|
||||
"gitea_repo_list_all_git_refs": "2xx",
|
||||
"gitea_repo_list_branch_protection": "2xx",
|
||||
"gitea_repo_list_branches": "2xx",
|
||||
"gitea_repo_list_collaborators": "2xx",
|
||||
"gitea_repo_list_forks": "2xx",
|
||||
"gitea_repo_list_git_refs": "2xx",
|
||||
"gitea_repo_list_keys": "2xx",
|
||||
"gitea_repo_list_project_column_issues": "2xx",
|
||||
"gitea_repo_list_project_columns": "2xx",
|
||||
"gitea_repo_list_projects": "2xx",
|
||||
"gitea_repo_list_push_mirrors": "2xx",
|
||||
"gitea_repo_list_stargazers": "2xx",
|
||||
"gitea_repo_list_statuses": "2xx",
|
||||
"gitea_repo_list_statuses_by_ref": "2xx",
|
||||
"gitea_repo_list_subscribers": "2xx",
|
||||
"gitea_repo_list_tag_protection": "2xx",
|
||||
"gitea_repo_list_tags": "2xx",
|
||||
"gitea_repo_list_teams": "2xx",
|
||||
"gitea_repo_list_topics": "2xx",
|
||||
"gitea_repo_list_user_tracked_times": "2xx",
|
||||
"gitea_repo_merge_upstream": "2xx",
|
||||
"gitea_repo_migrate": "2xx",
|
||||
"gitea_repo_mirror_sync": "2xx",
|
||||
"gitea_repo_move_project_columns": "2xx",
|
||||
"gitea_repo_move_project_issue": "2xx",
|
||||
"gitea_repo_new_pin_allowed": "2xx",
|
||||
"gitea_repo_push_mirror_sync": "2xx",
|
||||
"gitea_repo_put_subscription": "2xx",
|
||||
"gitea_repo_reject_repo_transfer": 404,
|
||||
"gitea_repo_remove_issue_from_project_column": "2xx",
|
||||
"gitea_repo_rename_branch": "2xx",
|
||||
"gitea_repo_search": "2xx",
|
||||
"gitea_repo_set_default_project_column": "2xx",
|
||||
"gitea_repo_signing_key": 404,
|
||||
"gitea_repo_signing_key_ssh": 404,
|
||||
"gitea_repo_tracked_times": "2xx",
|
||||
"gitea_repo_transfer": "2xx",
|
||||
"gitea_repo_update_avatar": "2xx",
|
||||
"gitea_repo_update_branch": "2xx",
|
||||
"gitea_repo_update_branch_protection_priorities": "2xx",
|
||||
"gitea_repo_update_file": "2xx",
|
||||
"gitea_repo_update_topics": "2xx",
|
||||
"gitea_repo_validate_issue_config": "2xx",
|
||||
"gitea_team_add_team_member": "2xx",
|
||||
"gitea_team_add_team_repository": "2xx",
|
||||
"gitea_team_create_team": "2xx",
|
||||
"gitea_team_delete_team": "2xx",
|
||||
"gitea_team_edit_team": "2xx",
|
||||
"gitea_team_get_team": "2xx",
|
||||
"gitea_team_list_team_activity_feeds": "2xx",
|
||||
"gitea_team_list_team_member": "2xx",
|
||||
"gitea_team_list_team_members": "2xx",
|
||||
"gitea_team_list_team_repo": "2xx",
|
||||
"gitea_team_list_team_repos": "2xx",
|
||||
"gitea_team_list_teams": "2xx",
|
||||
"gitea_team_remove_team_member": "2xx",
|
||||
"gitea_team_remove_team_repository": "2xx",
|
||||
"gitea_team_search": "2xx",
|
||||
"gitea_user_add_email": "2xx",
|
||||
"gitea_user_block_user": "2xx",
|
||||
"gitea_user_check_following": "2xx",
|
||||
"gitea_user_check_user_block": "2xx",
|
||||
"gitea_user_create_oauth2_application": "2xx",
|
||||
"gitea_user_create_token": 401,
|
||||
"gitea_user_current_add_issue_to_project_column": "2xx",
|
||||
"gitea_user_current_check_following": "2xx",
|
||||
"gitea_user_current_check_starring": "2xx",
|
||||
"gitea_user_current_create_project": "2xx",
|
||||
"gitea_user_current_create_project_column": "2xx",
|
||||
"gitea_user_current_create_repo": "2xx",
|
||||
"gitea_user_current_delete_follow": "2xx",
|
||||
"gitea_user_current_delete_gpg_key": "2xx",
|
||||
"gitea_user_current_delete_key": "2xx",
|
||||
"gitea_user_current_delete_project": "2xx",
|
||||
"gitea_user_current_delete_project_column": "2xx",
|
||||
"gitea_user_current_delete_star": "2xx",
|
||||
"gitea_user_current_edit_project": "2xx",
|
||||
"gitea_user_current_edit_project_column": "2xx",
|
||||
"gitea_user_current_get_gpg_key": "2xx",
|
||||
"gitea_user_current_get_key": "2xx",
|
||||
"gitea_user_current_get_project": "2xx",
|
||||
"gitea_user_current_get_project_column": "2xx",
|
||||
"gitea_user_current_list_followers": "2xx",
|
||||
"gitea_user_current_list_following": "2xx",
|
||||
"gitea_user_current_list_gpg_keys": "2xx",
|
||||
"gitea_user_current_list_keys": "2xx",
|
||||
"gitea_user_current_list_orgs": "2xx",
|
||||
"gitea_user_current_list_project_column_issues": "2xx",
|
||||
"gitea_user_current_list_project_columns": "2xx",
|
||||
"gitea_user_current_list_projects": "2xx",
|
||||
"gitea_user_current_list_repos": "2xx",
|
||||
"gitea_user_current_list_starred": "2xx",
|
||||
"gitea_user_current_list_subscriptions": "2xx",
|
||||
"gitea_user_current_move_project_columns": "2xx",
|
||||
"gitea_user_current_move_project_issue": "2xx",
|
||||
"gitea_user_current_post_gpg_key": "2xx",
|
||||
"gitea_user_current_post_key": "2xx",
|
||||
"gitea_user_current_put_follow": "2xx",
|
||||
"gitea_user_current_put_star": "2xx",
|
||||
"gitea_user_current_remove_issue_from_project_column": "2xx",
|
||||
"gitea_user_current_set_default_project_column": "2xx",
|
||||
"gitea_user_current_tracked_times": "2xx",
|
||||
"gitea_user_delete_access_token": 401,
|
||||
"gitea_user_delete_avatar": "2xx",
|
||||
"gitea_user_delete_email": "2xx",
|
||||
"gitea_user_delete_oauth2_application": "2xx",
|
||||
"gitea_user_get": "2xx",
|
||||
"gitea_user_get_current": "2xx",
|
||||
"gitea_user_get_heatmap_data": "2xx",
|
||||
"gitea_user_get_oauth2_application": "2xx",
|
||||
"gitea_user_get_org_permissions": "2xx",
|
||||
"gitea_user_get_settings": "2xx",
|
||||
"gitea_user_get_stopwatches": "2xx",
|
||||
"gitea_user_get_tokens": 401,
|
||||
"gitea_user_get_verification_token": "2xx",
|
||||
"gitea_user_list_activity_feeds": "2xx",
|
||||
"gitea_user_list_blocks": "2xx",
|
||||
"gitea_user_list_emails": "2xx",
|
||||
"gitea_user_list_followers": "2xx",
|
||||
"gitea_user_list_following": "2xx",
|
||||
"gitea_user_list_gpg_keys": "2xx",
|
||||
"gitea_user_list_keys": "2xx",
|
||||
"gitea_user_list_oauth2_applications": "2xx",
|
||||
"gitea_user_list_orgs": "2xx",
|
||||
"gitea_user_list_projects": "2xx",
|
||||
"gitea_user_list_repos": "2xx",
|
||||
"gitea_user_list_starred": "2xx",
|
||||
"gitea_user_list_subscriptions": "2xx",
|
||||
"gitea_user_list_teams": "2xx",
|
||||
"gitea_user_search": "2xx",
|
||||
"gitea_user_unblock_user": "2xx",
|
||||
"gitea_user_update_avatar": "2xx",
|
||||
"gitea_user_update_oauth2_application": "2xx",
|
||||
"gitea_user_update_settings": "2xx",
|
||||
"gitea_user_verify_gpg_key": 422,
|
||||
"gitea_wiki_create_wiki_page": "2xx",
|
||||
"gitea_wiki_delete_wiki_page": "2xx",
|
||||
"gitea_wiki_edit_wiki_page": "2xx",
|
||||
"gitea_wiki_get_wiki_page": "2xx",
|
||||
"gitea_wiki_get_wiki_page_revisions": "2xx",
|
||||
"gitea_wiki_get_wiki_pages": "2xx"
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@ that is data or pure logic, so it can be checked without Docker
|
||||
then deletes (leaf resources before their parents).
|
||||
* :func:`classify` decides whether a result is acceptable: a 2xx, a status the swagger
|
||||
documents for that operation, or an explicit entry of :data:`ALLOWANCES` with a reason.
|
||||
* :func:`compare_baseline` pins the outcome class of every tool (``2xx`` or the exact
|
||||
status of a documented non-2xx or an allowance) in ``sweep_baseline.json`` and reports
|
||||
every downgrade at once; ``AEGIS_IT_WRITE_BASELINE=1`` regenerates the file.
|
||||
* :func:`render_report` writes ``docs/integration-coverage.md``.
|
||||
"""
|
||||
|
||||
@@ -27,12 +30,15 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from aegis_gitea_mcp.catalog import Catalog, Operation
|
||||
from tests.integration.static_keys import GPG_PUBLIC_KEYS
|
||||
from tests.integration.sweep_seed import PNG_1X1, Ctx, b64, ssh_public_key
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
SPEC_PATH = REPO_ROOT / "spec" / "gitea-swagger.v1.json"
|
||||
REPORT_PATH = REPO_ROOT / "docs" / "integration-coverage.md"
|
||||
REPORT_ENV = "AEGIS_IT_WRITE_REPORT"
|
||||
BASELINE_PATH = Path(__file__).resolve().parent / "sweep_baseline.json"
|
||||
BASELINE_ENV = "AEGIS_IT_WRITE_BASELINE"
|
||||
RAW_TOOL = "gitea_api_request"
|
||||
|
||||
#: Tools the generic sweep never calls, with the test that covers them in isolation.
|
||||
@@ -172,6 +178,7 @@ ID_RULES: list[tuple[str, str]] = [
|
||||
(r"^/orgs/\{org\}/projects/\{id\}", "org_project"),
|
||||
(r"^/user/hooks/\{id\}", "user_hook"),
|
||||
(r"^/user/keys/\{id\}", "user_key"),
|
||||
(r"^/user/gpg_keys/\{id\}", "gpg_key"),
|
||||
(r"^/user/projects/\{id\}", "user_project"),
|
||||
(r"^/user/applications/oauth2/\{id\}", "oauth_app"),
|
||||
(r"^/admin/hooks/\{id\}", "admin_hook"),
|
||||
@@ -605,7 +612,9 @@ OVERRIDES: dict[str, Override] = {
|
||||
"gitea_user_current_post_key": lambda c: {
|
||||
"body": {"title": fresh(c, "key"), "key": ssh_public_key()}
|
||||
},
|
||||
"gitea_user_current_post_gpg_key": lambda c: {"body": {"armored_public_key": "not a key"}},
|
||||
"gitea_user_current_post_gpg_key": lambda c: {
|
||||
"body": {"armored_public_key": GPG_PUBLIC_KEYS[1]}
|
||||
},
|
||||
"gitea_user_delete_access_token": lambda c: {
|
||||
"username": c["admin"],
|
||||
"token": c["throwaway_token"],
|
||||
@@ -858,6 +867,88 @@ def write_report_if_enabled(catalog: Catalog, gitea_version: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Baseline: the pinned outcome class per tool
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SUCCESS = "2xx"
|
||||
NO_STATUS = "no-status"
|
||||
Outcome = str | int
|
||||
|
||||
|
||||
def outcome_class(record: SweepRecord) -> Outcome:
|
||||
"""``"2xx"`` for a success, else the HTTP status Gitea answered (or ``"no-status"``)."""
|
||||
if record.status is not None and 200 <= record.status < 300 and not record.is_error:
|
||||
return SUCCESS
|
||||
return record.status if record.status is not None else NO_STATUS
|
||||
|
||||
|
||||
def baseline_snapshot(records: Iterable[SweepRecord], gitea_version: str) -> dict[str, Any]:
|
||||
"""The baseline document for ``records`` (what ``AEGIS_IT_WRITE_BASELINE=1`` writes)."""
|
||||
return {
|
||||
"gitea": gitea_version,
|
||||
"comment": (
|
||||
"Expected outcome class per tool in the sweep: 2xx, or the exact status of a "
|
||||
"documented non-2xx or allowance. Regenerate with AEGIS_IT_WRITE_BASELINE=1."
|
||||
),
|
||||
"tools": {record.tool: outcome_class(record) for record in records},
|
||||
}
|
||||
|
||||
|
||||
def load_baseline(path: Path = BASELINE_PATH) -> dict[str, Outcome]:
|
||||
"""Read the pinned outcome per tool."""
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
tools: dict[str, Outcome] = data["tools"]
|
||||
return tools
|
||||
|
||||
|
||||
def write_baseline(
|
||||
records: Iterable[SweepRecord], gitea_version: str, path: Path = BASELINE_PATH
|
||||
) -> None:
|
||||
"""Write the baseline file (tools sorted by name, so diffs show only real changes)."""
|
||||
document = baseline_snapshot(records, gitea_version)
|
||||
document["tools"] = dict(sorted(document["tools"].items()))
|
||||
path.write_text(json.dumps(document, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaselineDiff:
|
||||
"""Result of comparing a sweep with the pinned baseline."""
|
||||
|
||||
regressions: list[str] = field(default_factory=list)
|
||||
upgrades: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def compare_baseline(records: Iterable[SweepRecord], baseline: dict[str, Outcome]) -> BaselineDiff:
|
||||
"""Compare outcomes with the baseline.
|
||||
|
||||
A regression is a 2xx that is no longer a 2xx, a documented non-2xx that answers a
|
||||
different status, a tool without a baseline entry or a baseline entry without a tool.
|
||||
A non-2xx that became a 2xx is an upgrade: reported, but it does not fail the run.
|
||||
"""
|
||||
diff = BaselineDiff()
|
||||
seen: set[str] = set()
|
||||
for record in records:
|
||||
seen.add(record.tool)
|
||||
actual = outcome_class(record)
|
||||
if record.tool not in baseline:
|
||||
diff.regressions.append(f"{record.tool}: no baseline entry (got {actual})")
|
||||
continue
|
||||
expected = baseline[record.tool]
|
||||
if actual == expected:
|
||||
continue
|
||||
if actual == SUCCESS:
|
||||
diff.upgrades.append(f"{record.tool}: baseline {expected}, now 2xx")
|
||||
continue
|
||||
detail = f"{record.tool}: baseline {expected}, got {actual} ({record.method} {record.path}"
|
||||
if record.message:
|
||||
detail += f" :: {record.message}"
|
||||
diff.regressions.append(detail + ")")
|
||||
for tool in sorted(set(baseline) - seen):
|
||||
diff.regressions.append(f"{tool}: in the baseline but not called by the sweep")
|
||||
return diff
|
||||
|
||||
|
||||
def failing(records: Iterable[SweepRecord]) -> list[SweepRecord]:
|
||||
"""The records whose outcome is ``unexpected``."""
|
||||
return [record for record in records if record.outcome == "unexpected"]
|
||||
|
||||
@@ -16,6 +16,7 @@ from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||
|
||||
from tests.integration.harness import GiteaInstance, HarnessError
|
||||
from tests.integration.static_keys import GPG_EMAILS, GPG_PUBLIC_KEYS
|
||||
|
||||
Ctx = dict[str, Any]
|
||||
|
||||
@@ -370,7 +371,11 @@ def seed(gitea: GiteaInstance, user: str, user_token: str) -> Ctx:
|
||||
|
||||
# Current user: email, follow, star, watch, block.
|
||||
c["email"] = f"sweep-{sfx}@example.test"
|
||||
s.call("POST", "/user/emails", json={"emails": [c["email"]]})
|
||||
s.call("POST", "/user/emails", json={"emails": [c["email"], *GPG_EMAILS]})
|
||||
# Gitea only accepts a GPG key whose identity matches an email of the account.
|
||||
c["gpg_key"] = s.call(
|
||||
"POST", "/user/gpg_keys", json={"armored_public_key": GPG_PUBLIC_KEYS[0]}
|
||||
)["id"]
|
||||
s.call("PUT", f"/user/following/{user}")
|
||||
s.call("PUT", f"/user/starred/{admin}/{repo}")
|
||||
s.call("PUT", f"{base}/subscription")
|
||||
@@ -419,8 +424,12 @@ def _project(s: Seeder, c: Ctx, scope: str, path: str) -> None:
|
||||
|
||||
|
||||
def _find_run(s: Seeder, base: str) -> tuple[Any, Any]:
|
||||
"""Return ``(run id, job id)`` of the first workflow run, or ``(1, 1)`` if none appeared."""
|
||||
deadline = time.monotonic() + 10
|
||||
"""Return ``(run id, job id)`` of the first workflow run; fail when none appears.
|
||||
|
||||
A made-up id would only turn every dependent tool into a 404, which the sweep would
|
||||
accept as documented, so a missing run is a seeding failure.
|
||||
"""
|
||||
deadline = time.monotonic() + 30
|
||||
while time.monotonic() < deadline:
|
||||
runs = s.call("GET", f"{base}/actions/runs")
|
||||
entries = runs.get("workflow_runs") or []
|
||||
@@ -431,15 +440,15 @@ def _find_run(s: Seeder, base: str) -> tuple[Any, Any]:
|
||||
if job_entries:
|
||||
return run_id, job_entries[0]["id"]
|
||||
time.sleep(0.5)
|
||||
return 1, 1
|
||||
raise HarnessError("seed: the push of the workflow file did not create a workflow run")
|
||||
|
||||
|
||||
def _find_notification(s: Seeder, admin: str) -> Any:
|
||||
"""Return the id of a notification of the admin if one exists, else 1."""
|
||||
deadline = time.monotonic() + 5
|
||||
"""Return the id of a notification of the admin; fail when none appears."""
|
||||
deadline = time.monotonic() + 30
|
||||
while time.monotonic() < deadline:
|
||||
listing = s.call("GET", "/notifications", params={"all": "true"})
|
||||
if listing:
|
||||
return listing[0]["id"]
|
||||
time.sleep(0.5)
|
||||
return 1
|
||||
raise HarnessError(f"seed: no notification arrived for {admin}")
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from aegis_gitea_mcp.audit import validate_audit_log_integrity
|
||||
@@ -9,20 +11,20 @@ from aegis_gitea_mcp.catalog import load_catalog
|
||||
from tests.integration.conftest import McpSession, RegularUser
|
||||
from tests.integration.harness import GiteaInstance
|
||||
from tests.integration.sweep_plan import (
|
||||
BASELINE_ENV,
|
||||
EXCLUDED,
|
||||
LOG,
|
||||
RAW_TOOL,
|
||||
compare_baseline,
|
||||
failing,
|
||||
load_baseline,
|
||||
load_spec,
|
||||
run_sweep,
|
||||
summarize,
|
||||
write_baseline,
|
||||
)
|
||||
from tests.integration.sweep_seed import Ctx, seed
|
||||
|
||||
#: At least this share of the tools must get a real 2xx, so a systematic argument problem
|
||||
#: cannot hide behind documented 404 responses.
|
||||
MIN_SUCCESS_SHARE = 0.55
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def sweep_context(gitea: GiteaInstance, regular_user: RegularUser) -> Ctx:
|
||||
@@ -53,8 +55,23 @@ async def test_every_tool_reaches_gitea(
|
||||
for r in bad
|
||||
)
|
||||
assert not bad, f"{len(bad)} tool(s) with an unexpected result ({summary}):\n{report}"
|
||||
success_share = summary.get("success", 0) / len(records)
|
||||
assert success_share >= MIN_SUCCESS_SHARE, f"only {success_share:.0%} of tools got a 2xx"
|
||||
|
||||
# The pinned outcome per tool: an argument or path regression cannot hide behind a
|
||||
# documented 404, because a 2xx that turns into any other status fails here.
|
||||
if os.environ.get(BASELINE_ENV) == "1":
|
||||
write_baseline(records, load_catalog().spec_version)
|
||||
else:
|
||||
diff = compare_baseline(records, load_baseline())
|
||||
if diff.upgrades:
|
||||
print(
|
||||
f"\n{len(diff.upgrades)} tool(s) improved on the baseline; refresh it with "
|
||||
f"{BASELINE_ENV}=1:\n " + "\n ".join(diff.upgrades)
|
||||
)
|
||||
assert not diff.regressions, (
|
||||
f"{len(diff.regressions)} tool(s) differ from sweep_baseline.json "
|
||||
f"(regenerate with {BASELINE_ENV}=1 only when the change is intended):\n "
|
||||
+ "\n ".join(diff.regressions)
|
||||
)
|
||||
|
||||
# Whatever the 538 calls did, no credential or secret value came back in a result.
|
||||
hidden = [
|
||||
|
||||
@@ -271,3 +271,49 @@ def test_report_is_only_written_when_enabled(
|
||||
]
|
||||
assert sweep_plan.write_report_if_enabled(CATALOG, "28.1.0") is True
|
||||
assert target.read_text(encoding="utf-8").startswith("# Integration coverage")
|
||||
|
||||
|
||||
def _record(tool: str, status: int | None, is_error: bool = False) -> SweepRecord:
|
||||
return SweepRecord(tool, "repo", "GET", "/x", status, is_error, None, "", "success")
|
||||
|
||||
|
||||
def test_outcome_class_is_2xx_or_the_exact_status() -> None:
|
||||
assert sweep_plan.outcome_class(_record("t", 200)) == "2xx"
|
||||
assert sweep_plan.outcome_class(_record("t", 204)) == "2xx"
|
||||
assert sweep_plan.outcome_class(_record("t", 404, True)) == 404
|
||||
assert sweep_plan.outcome_class(_record("t", 200, True)) == 200 # an error flag wins
|
||||
assert sweep_plan.outcome_class(_record("t", None, True)) == "no-status"
|
||||
|
||||
|
||||
def test_baseline_reports_every_regression_at_once_and_notes_upgrades() -> None:
|
||||
baseline: dict[str, Any] = {"a": "2xx", "b": 404, "c": 404, "d": "2xx", "gone": "2xx"}
|
||||
records = [
|
||||
_record("a", 404, True), # 2xx -> 404: regression
|
||||
_record("b", 200), # 404 -> 2xx: upgrade
|
||||
_record("c", 403, True), # 404 -> 403: a different status is a regression
|
||||
_record("d", 200), # unchanged
|
||||
_record("new", 200), # not in the baseline
|
||||
]
|
||||
|
||||
diff = sweep_plan.compare_baseline(records, baseline)
|
||||
|
||||
assert diff.upgrades == ["b: baseline 404, now 2xx"]
|
||||
assert len(diff.regressions) == 4
|
||||
assert diff.regressions[0].startswith("a: baseline 2xx, got 404")
|
||||
assert diff.regressions[1].startswith("c: baseline 404, got 403")
|
||||
assert "new: no baseline entry" in diff.regressions[2]
|
||||
assert diff.regressions[3] == "gone: in the baseline but not called by the sweep"
|
||||
|
||||
|
||||
def test_baseline_round_trips_and_covers_the_whole_sweep(tmp_path: Any) -> None:
|
||||
target = tmp_path / "baseline.json"
|
||||
records = [_record("b", 404, True), _record("a", 200)]
|
||||
sweep_plan.write_baseline(records, "28.1.0", target)
|
||||
assert sweep_plan.load_baseline(target) == {"a": "2xx", "b": 404}
|
||||
assert list(sweep_plan.load_baseline(target)) == ["a", "b"]
|
||||
|
||||
# The committed baseline names exactly the tools the sweep calls.
|
||||
committed = sweep_plan.load_baseline()
|
||||
swept = {op.tool_name for op in ordered_operations(CATALOG)} | {RAW_TOOL}
|
||||
assert set(committed) == swept
|
||||
assert all(value == "2xx" or isinstance(value, int) for value in committed.values())
|
||||
Reference in new issue
Block a user