Dev #72

Merged
Latte merged 71 commits from dev into main 2026-10-14 17:52:23 +00:00
6 changed files with 761 additions and 14 deletions
Showing only changes of commit 9e51f72ec9 - Show all commits

No files matched your search

+41
View File
@@ -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-----
""",
)
+543
View File
@@ -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"
}
}
+92 -1
View File
@@ -12,6 +12,9 @@ that is data or pure logic, so it can be checked without Docker
then deletes (leaf resources before their parents). then deletes (leaf resources before their parents).
* :func:`classify` decides whether a result is acceptable: a 2xx, a status the swagger * :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. 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``. * :func:`render_report` writes ``docs/integration-coverage.md``.
""" """
@@ -27,12 +30,15 @@ from pathlib import Path
from typing import Any from typing import Any
from aegis_gitea_mcp.catalog import Catalog, Operation 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 from tests.integration.sweep_seed import PNG_1X1, Ctx, b64, ssh_public_key
REPO_ROOT = Path(__file__).resolve().parents[2] REPO_ROOT = Path(__file__).resolve().parents[2]
SPEC_PATH = REPO_ROOT / "spec" / "gitea-swagger.v1.json" SPEC_PATH = REPO_ROOT / "spec" / "gitea-swagger.v1.json"
REPORT_PATH = REPO_ROOT / "docs" / "integration-coverage.md" REPORT_PATH = REPO_ROOT / "docs" / "integration-coverage.md"
REPORT_ENV = "AEGIS_IT_WRITE_REPORT" 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" RAW_TOOL = "gitea_api_request"
#: Tools the generic sweep never calls, with the test that covers them in isolation. #: 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"^/orgs/\{org\}/projects/\{id\}", "org_project"),
(r"^/user/hooks/\{id\}", "user_hook"), (r"^/user/hooks/\{id\}", "user_hook"),
(r"^/user/keys/\{id\}", "user_key"), (r"^/user/keys/\{id\}", "user_key"),
(r"^/user/gpg_keys/\{id\}", "gpg_key"),
(r"^/user/projects/\{id\}", "user_project"), (r"^/user/projects/\{id\}", "user_project"),
(r"^/user/applications/oauth2/\{id\}", "oauth_app"), (r"^/user/applications/oauth2/\{id\}", "oauth_app"),
(r"^/admin/hooks/\{id\}", "admin_hook"), (r"^/admin/hooks/\{id\}", "admin_hook"),
@@ -605,7 +612,9 @@ OVERRIDES: dict[str, Override] = {
"gitea_user_current_post_key": lambda c: { "gitea_user_current_post_key": lambda c: {
"body": {"title": fresh(c, "key"), "key": ssh_public_key()} "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: { "gitea_user_delete_access_token": lambda c: {
"username": c["admin"], "username": c["admin"],
"token": c["throwaway_token"], "token": c["throwaway_token"],
@@ -858,6 +867,88 @@ def write_report_if_enabled(catalog: Catalog, gitea_version: str) -> bool:
return True 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]: def failing(records: Iterable[SweepRecord]) -> list[SweepRecord]:
"""The records whose outcome is ``unexpected``.""" """The records whose outcome is ``unexpected``."""
return [record for record in records if record.outcome == "unexpected"] return [record for record in records if record.outcome == "unexpected"]
+16 -7
View File
@@ -16,6 +16,7 @@ from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from tests.integration.harness import GiteaInstance, HarnessError from tests.integration.harness import GiteaInstance, HarnessError
from tests.integration.static_keys import GPG_EMAILS, GPG_PUBLIC_KEYS
Ctx = dict[str, Any] 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. # Current user: email, follow, star, watch, block.
c["email"] = f"sweep-{sfx}@example.test" 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/following/{user}")
s.call("PUT", f"/user/starred/{admin}/{repo}") s.call("PUT", f"/user/starred/{admin}/{repo}")
s.call("PUT", f"{base}/subscription") 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]: 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.""" """Return ``(run id, job id)`` of the first workflow run; fail when none appears.
deadline = time.monotonic() + 10
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: while time.monotonic() < deadline:
runs = s.call("GET", f"{base}/actions/runs") runs = s.call("GET", f"{base}/actions/runs")
entries = runs.get("workflow_runs") or [] entries = runs.get("workflow_runs") or []
@@ -431,15 +440,15 @@ def _find_run(s: Seeder, base: str) -> tuple[Any, Any]:
if job_entries: if job_entries:
return run_id, job_entries[0]["id"] return run_id, job_entries[0]["id"]
time.sleep(0.5) 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: def _find_notification(s: Seeder, admin: str) -> Any:
"""Return the id of a notification of the admin if one exists, else 1.""" """Return the id of a notification of the admin; fail when none appears."""
deadline = time.monotonic() + 5 deadline = time.monotonic() + 30
while time.monotonic() < deadline: while time.monotonic() < deadline:
listing = s.call("GET", "/notifications", params={"all": "true"}) listing = s.call("GET", "/notifications", params={"all": "true"})
if listing: if listing:
return listing[0]["id"] return listing[0]["id"]
time.sleep(0.5) time.sleep(0.5)
return 1 raise HarnessError(f"seed: no notification arrived for {admin}")
+23 -6
View File
@@ -2,6 +2,8 @@
from __future__ import annotations from __future__ import annotations
import os
import pytest import pytest
from aegis_gitea_mcp.audit import validate_audit_log_integrity 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.conftest import McpSession, RegularUser
from tests.integration.harness import GiteaInstance from tests.integration.harness import GiteaInstance
from tests.integration.sweep_plan import ( from tests.integration.sweep_plan import (
BASELINE_ENV,
EXCLUDED, EXCLUDED,
LOG, LOG,
RAW_TOOL, RAW_TOOL,
compare_baseline,
failing, failing,
load_baseline,
load_spec, load_spec,
run_sweep, run_sweep,
summarize, summarize,
write_baseline,
) )
from tests.integration.sweep_seed import Ctx, seed 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") @pytest.fixture(scope="module")
def sweep_context(gitea: GiteaInstance, regular_user: RegularUser) -> Ctx: def sweep_context(gitea: GiteaInstance, regular_user: RegularUser) -> Ctx:
@@ -53,8 +55,23 @@ async def test_every_tool_reaches_gitea(
for r in bad for r in bad
) )
assert not bad, f"{len(bad)} tool(s) with an unexpected result ({summary}):\n{report}" 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. # Whatever the 538 calls did, no credential or secret value came back in a result.
hidden = [ hidden = [
+46
View File
@@ -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 sweep_plan.write_report_if_enabled(CATALOG, "28.1.0") is True
assert target.read_text(encoding="utf-8").startswith("# Integration coverage") 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())