From 8abda623122ae187df3d8ed339cfd656a86c29be Mon Sep 17 00:00:00 2001
From: silverwind
Date: Sat, 24 Oct 2026 15:48:19 +0200
Subject: [PATCH] test: consolidate Go tests (#39962)
Consolidate all Go tests. Coverage is identical to before. Go tests are
around 5% faster while being 7% smaller. No non-test code is touched.
---
build/openapi3gen/convert_test.go | 60 +-
build/openapi3gen/enumscan_test.go | 251 ++--
cmd/admin_auth_ldap_test.go | 471 +-----
cmd/admin_auth_oauth_test.go | 154 +-
cmd/admin_auth_smtp_test.go | 34 +-
cmd/admin_user_delete_test.go | 57 +-
cmd/admin_user_must_change_password_test.go | 81 +-
cmd/cmdtest/cmd_test.go | 130 +-
modelmigration/base/db_test.go | 39 +-
modelmigration/v1_20/v259_test.go | 7 +-
modelmigration/v1_26/v329_test.go | 24 +-
models/actions/run_job_status_test.go | 29 +-
models/actions/run_job_test.go | 512 +++----
models/actions/runner_test.go | 90 +-
models/actions/status_test.go | 47 +-
models/actions/task_test.go | 226 ++-
models/actions/utils_test.go | 22 +-
models/activities/action_test.go | 21 +-
models/activities/notification_test.go | 28 +-
models/asymkey/gpg_key_test.go | 41 +-
models/asymkey/ssh_key_test.go | 52 +-
models/audit/audit_event_test.go | 38 +-
models/auth/access_token_test.go | 258 ++--
models/auth/oauth2_test.go | 231 ++-
models/auth/twofactor_test.go | 113 +-
models/auth/webauthn_test.go | 74 +-
models/db/context_test.go | 24 +-
models/db/index_test.go | 37 +-
models/dbfs/dbfs_test.go | 88 +-
models/git/commit_status_test.go | 149 +-
models/git/protected_branch_list_test.go | 89 +-
models/git/protected_branch_test.go | 360 ++---
models/git/protected_tag_test.go | 109 +-
models/issues/comment_test.go | 316 ++--
models/issues/issue_list_test.go | 216 +--
models/issues/issue_project_multi_test.go | 21 +-
models/issues/issue_test.go | 395 +++--
models/issues/issue_user_test.go | 78 +-
models/issues/issue_watch_test.go | 89 +-
models/issues/label_test.go | 391 +++--
models/issues/milestone_test.go | 392 ++---
models/issues/review_test.go | 35 +-
models/organization/org_test.go | 225 +--
models/organization/team_test.go | 23 +-
models/packages/package_test.go | 40 +-
models/perm/access/repo_permission_test.go | 112 +-
models/project/project_test.go | 16 +-
models/renderhelper/repo_file_test.go | 35 +-
models/renderhelper/simple_document_test.go | 2 -
models/repo/archive_test.go | 32 -
models/repo/attachment_test.go | 130 +-
models/repo/avatar_test.go | 16 +
models/repo/collaboration_test.go | 191 ++-
models/repo/pull_request_default_test.go | 35 +-
models/repo/pushmirror_test.go | 17 +-
models/repo/redirect_test.go | 78 +-
models/repo/release_test.go | 136 +-
models/repo/repo_test.go | 51 +-
models/repo/star_test.go | 85 +-
models/repo/watch_test.go | 59 +-
models/secret/secret_test.go | 197 +--
models/system/setting_test.go | 24 +-
models/unit/unit_test.go | 51 +-
models/user/email_address_test.go | 233 ++-
models/user/openid_test.go | 98 +-
models/user/user_test.go | 118 +-
models/webhook/webhook_test.go | 159 +-
modules/actions/github_test.go | 6 -
modules/actions/jobparser/jobparser_test.go | 99 +-
modules/actions/jobparser/model_test.go | 26 +-
.../workflowpattern/workflow_pattern_test.go | 270 +---
modules/actions/workflows_test.go | 75 +-
modules/assetfs/layered_test.go | 36 +-
modules/auth/password/password_test.go | 8 +-
modules/auth/password/pwn/pwn_test.go | 34 +-
modules/avatar/avatar_test.go | 29 +-
modules/charset/charset_test.go | 126 +-
modules/commitstatus/commit_status_test.go | 5 -
modules/csv/csv_test.go | 41 +-
modules/fileicon/render_test.go | 80 +-
modules/git/attribute/batch_test.go | 91 +-
modules/git/blame_sha256_test.go | 148 --
modules/git/blame_test.go | 277 ++--
modules/git/commit2_test.go | 51 +-
modules/git/commit_file_test.go | 94 +-
modules/git/commit_sha256_test.go | 70 -
modules/git/commit_test.go | 150 +-
modules/git/compare_test.go | 117 +-
modules/git/grep_test.go | 68 +-
modules/git/parse_treeentry_test.go | 18 +-
modules/git/repo_blob_test.go | 43 +-
modules/git/repo_branch_test.go | 124 +-
modules/git/repo_commit_test.go | 196 ++-
modules/git/repo_tag_test.go | 67 +-
modules/git/signature_test.go | 45 +-
modules/git/tree_entry_common_test.go | 40 +-
modules/git/url/url_test.go | 210 +--
modules/highlight/highlight_test.go | 81 +-
modules/httpcache/httpcache_test.go | 26 +-
modules/httplib/serve_test.go | 130 +-
modules/indexer/issues/bleve/bleve_test.go | 137 +-
.../issues/meilisearch/meilisearch_test.go | 1 -
modules/issue/template/template_test.go | 270 +---
modules/lfs/http_client_test.go | 285 ++--
modules/lfs/transferadapter_test.go | 79 +-
modules/log/event_format_test.go | 60 +-
modules/log/level_test.go | 30 +-
modules/markup/camo_test.go | 33 +-
modules/markup/html_internal_test.go | 10 -
modules/markup/html_issue_test.go | 55 +-
modules/markup/html_node_test.go | 113 +-
modules/markup/html_test.go | 386 ++---
modules/markup/internal/internal_test.go | 42 +-
modules/markup/jupyter/jupyter_test.go | 56 +-
modules/markup/markdown/markdown_test.go | 11 -
modules/markup/markdown/meta_test.go | 93 +-
modules/markup/render_test.go | 11 +-
modules/nosql/manager_redis_test.go | 90 +-
modules/optional/serialization_test.go | 152 +-
modules/packages/chef/metadata_test.go | 66 +-
modules/packages/composer/metadata_test.go | 59 +-
modules/packages/conan/reference_test.go | 96 +-
modules/packages/cran/metadata_test.go | 90 +-
modules/packages/debian/metadata_test.go | 13 +-
modules/packages/goproxy/metadata_test.go | 79 +-
modules/packages/multi_hasher_test.go | 46 +-
modules/packages/npm/creator_test.go | 219 +--
modules/packages/nuget/metadata_test.go | 64 +-
modules/packages/rpm/metadata_test.go | 59 +-
modules/packages/swift/metadata_test.go | 79 +-
modules/packages/vagrant/metadata_test.go | 66 +-
modules/paginator/paginator_test.go | 215 +--
modules/process/manager_test.go | 34 +-
modules/queue/workerqueue_test.go | 99 +-
modules/repository/commits_test.go | 65 +-
modules/repository/license_test.go | 87 +-
modules/setting/actions_test.go | 94 +-
modules/setting/attachment_test.go | 56 +-
modules/setting/config_env_test.go | 74 +-
modules/setting/global_lock_test.go | 70 +-
modules/setting/lfs_test.go | 84 +-
modules/setting/log_test.go | 107 +-
modules/setting/packages_test.go | 174 +--
modules/setting/repository_archive_test.go | 92 +-
modules/setting/service_test.go | 50 +-
modules/setting/storage_test.go | 174 +--
modules/sitemap/sitemap_test.go | 217 +--
modules/storage/minio_test.go | 56 +-
modules/storage/storage_test.go | 47 +-
modules/structs/hook_test.go | 59 +-
modules/structs/issue_test.go | 32 +-
modules/templates/helper_test.go | 97 +-
modules/templates/util_date_test.go | 13 +-
modules/templates/util_render_test.go | 32 +-
modules/util/diff_slice_test.go | 93 +-
.../filebuffer/file_backed_buffer_test.go | 18 +-
modules/util/io_test.go | 35 +-
modules/util/keypair_test.go | 5 -
modules/util/path_test.go | 23 +-
modules/util/slice_test.go | 2 -
modules/util/sync_test.go | 59 +-
modules/util/util_test.go | 39 +-
modules/validation/refname_test.go | 270 +---
modules/validation/validurl_test.go | 102 +-
modules/web/routemock_test.go | 66 +-
modules/web/router_test.go | 36 +-
modules/zstd/zstd_test.go | 116 +-
routers/api/packages/generic/generic_test.go | 79 +-
routers/api/v1/misc/markup_test.go | 14 +-
routers/api/v1/utils/hook_test.go | 83 +-
routers/web/admin/users_test.go | 224 +--
routers/web/repo/actions/view_test.go | 113 +-
routers/web/repo/issue_test.go | 348 +----
routers/web/repo/middlewares_test.go | 38 +-
routers/web/repo/pull_review_test.go | 61 +-
routers/web/repo/setting/settings_test.go | 151 +-
routers/web/shared/project/project_test.go | 31 +-
routers/web/user/home_test.go | 68 +-
.../web/user/setting/security/openid_test.go | 21 +-
services/actions/clear_tasks_test.go | 10 +-
services/actions/context_test.go | 42 +-
services/actions/helper_test.go | 69 +-
services/actions/job_emitter_test.go | 212 ++-
services/actions/notifier_helper_test.go | 56 +-
services/actions/permission_parser_test.go | 107 +-
services/actions/reusable_workflow_test.go | 103 +-
services/agit/agit_test.go | 60 +-
services/asymkey/ssh_key_test.go | 50 +-
services/audit/audit_test.go | 39 +-
services/audit/notifier_test.go | 88 +-
services/context/upload/upload_test.go | 179 +--
services/convert/git_commit_test.go | 13 +-
services/convert/notification_test.go | 67 +-
services/cron/tasks_test.go | 50 +-
services/feed/feed_test.go | 181 +--
services/forms/user_form_test.go | 33 +-
services/gitdiff/csv_test.go | 50 +-
services/gitdiff/git_diff_tree_test.go | 177 +--
services/gitdiff/gitdiff_parse_test.go | 44 +-
services/gitdiff/gitdiff_test.go | 340 ++---
services/gitdiff/submodule_test.go | 65 +-
services/issue/commit_test.go | 102 +-
services/issue/reaction_test.go | 35 +-
services/lfs/server_test.go | 32 +-
services/mailer/mail_test.go | 100 +-
services/migrations/gitea_uploader_test.go | 117 +-
services/migrations/gitlab_test.go | 87 +-
.../oauth2_provider/additional_scopes_test.go | 43 -
.../oauth2_provider/jwtsigningkey_test.go | 33 +
services/org/team_test.go | 56 +-
services/org/user_test.go | 25 +-
services/projects/issue_test.go | 176 +--
services/projects/workflow_test.go | 8 +-
services/pubsub/memory_test.go | 9 +-
services/pull/merge_test.go | 118 +-
services/pull/review_test.go | 24 +-
services/pull/reviewer_test.go | 8 +-
services/pull/update_test.go | 98 +-
services/release/release_test.go | 206 +--
services/repository/adopt_test.go | 18 +-
services/repository/avatar_test.go | 67 +-
services/repository/collaboration_test.go | 18 +-
services/repository/delete_test.go | 4 -
services/repository/files/diff_test.go | 68 +-
services/repository/files/tree_test.go | 34 +-
services/repository/gitgraph/graph_test.go | 56 +-
services/repository/license_test.go | 61 +-
services/repository/repository_test.go | 60 +-
services/repository/transfer_test.go | 46 +-
services/user/block_test.go | 9 +-
services/user/update_test.go | 60 +-
services/user/user_test.go | 19 +-
services/webhook/deliver_test.go | 73 +-
services/webhook/dingtalk_test.go | 86 +-
services/webhook/discord_test.go | 157 +-
services/webhook/feishu_test.go | 151 +-
services/webhook/general_test.go | 201 +--
services/webhook/matrix_test.go | 34 +-
services/webhook/msteams_test.go | 374 ++---
services/webhook/packagist_test.go | 250 +---
services/webhook/slack_test.go | 196 +--
services/webhook/telegram_test.go | 214 ++-
tests/integration/actions_concurrency_test.go | 769 +++-------
tests/integration/actions_delete_run_test.go | 74 +-
tests/integration/actions_job_test.go | 349 ++---
tests/integration/actions_job_token_test.go | 400 +++--
tests/integration/actions_log_test.go | 190 +--
tests/integration/actions_rerun_test.go | 131 +-
.../actions_reusable_workflow_test.go | 44 +-
.../integration/actions_runner_modify_test.go | 26 +-
.../actions_scoped_workflow_test.go | 114 +-
tests/integration/actions_trigger_test.go | 1297 ++++++++---------
tests/integration/actions_variables_test.go | 73 +-
tests/integration/admin_audit_test.go | 117 +-
tests/integration/admin_user_test.go | 102 +-
.../integration/api_actions_artifact_test.go | 192 +--
.../api_actions_artifact_v4_test.go | 128 +-
tests/integration/api_actions_run_test.go | 111 +-
tests/integration/api_admin_org_test.go | 100 +-
tests/integration/api_admin_packages_test.go | 44 -
tests/integration/api_admin_test.go | 391 ++---
tests/integration/api_branch_test.go | 65 +-
.../api_comment_attachment_test.go | 240 ++-
tests/integration/api_comment_test.go | 301 ++--
.../api_gitignore_templates_test.go | 60 +-
tests/integration/api_gpg_keys_test.go | 81 +-
tests/integration/api_httpsig_test.go | 153 +-
.../integration/api_issue_attachment_test.go | 187 +--
tests/integration/api_issue_config_test.go | 191 ++-
.../integration/api_issue_dependency_test.go | 232 ++-
tests/integration/api_issue_label_test.go | 270 ++--
tests/integration/api_issue_pin_test.go | 112 +-
tests/integration/api_issue_reaction_test.go | 231 ++-
tests/integration/api_issue_stopwatch_test.go | 83 +-
.../api_issue_subscription_test.go | 30 +-
tests/integration/api_issue_templates_test.go | 69 -
tests/integration/api_issue_test.go | 50 +
.../api_issue_tracked_time_test.go | 186 ++-
tests/integration/api_keys_test.go | 198 ++-
tests/integration/api_label_templates_test.go | 105 +-
.../integration/api_license_templates_test.go | 57 -
tests/integration/api_notification_test.go | 38 +-
tests/integration/api_org_avatar_test.go | 72 -
tests/integration/api_org_test.go | 201 ++-
tests/integration/api_packages_arch_test.go | 65 +-
tests/integration/api_packages_chef_test.go | 181 +--
.../integration/api_packages_composer_test.go | 65 +-
tests/integration/api_packages_conan_test.go | 277 ++--
tests/integration/api_packages_conda_test.go | 152 +-
.../api_packages_container_test.go | 99 +-
tests/integration/api_packages_cran_test.go | 50 +-
.../integration/api_packages_goproxy_test.go | 51 +-
tests/integration/api_packages_maven_test.go | 50 +-
tests/integration/api_packages_nuget_test.go | 361 ++---
tests/integration/api_packages_pub_test.go | 21 +-
tests/integration/api_packages_pypi_test.go | 97 +-
tests/integration/api_packages_rpm_test.go | 141 +-
tests/integration/api_packages_swift_test.go | 266 ++--
tests/integration/api_packages_test.go | 127 +-
tests/integration/api_private_serv_test.go | 112 +-
tests/integration/api_public_only_test.go | 147 +-
tests/integration/api_pull_commits_test.go | 42 -
tests/integration/api_pull_review_test.go | 141 +-
tests/integration/api_pull_test.go | 60 +-
tests/integration/api_releases_test.go | 121 +-
tests/integration/api_repo_avatar_test.go | 59 +-
tests/integration/api_repo_branch_test.go | 217 ++-
.../integration/api_repo_collaborator_test.go | 139 +-
tests/integration/api_repo_compare_test.go | 37 +-
tests/integration/api_repo_delete_test.go | 54 -
.../integration/api_repo_file_create_test.go | 87 +-
.../integration/api_repo_file_delete_test.go | 91 +-
tests/integration/api_repo_file_get_test.go | 23 +-
.../integration/api_repo_file_update_test.go | 91 +-
.../integration/api_repo_files_change_test.go | 314 ++--
tests/integration/api_repo_git_blobs_test.go | 5 -
.../integration/api_repo_git_commits_test.go | 146 +-
tests/integration/api_repo_git_hook_test.go | 134 +-
tests/integration/api_repo_git_notes_test.go | 45 -
tests/integration/api_repo_git_ref_test.go | 39 -
tests/integration/api_repo_git_tags_test.go | 134 +-
tests/integration/api_repo_lfs_locks_test.go | 274 ++--
tests/integration/api_repo_lfs_test.go | 749 +++++-----
tests/integration/api_repo_secrets_test.go | 101 +-
tests/integration/api_repo_test.go | 812 +++++------
tests/integration/api_repo_topic_test.go | 321 ++--
...pi_repository_creation_token_scope_test.go | 9 +-
tests/integration/api_team_test.go | 569 ++++----
tests/integration/api_token_self_test.go | 103 +-
tests/integration/api_token_test.go | 690 +++------
tests/integration/api_twofa_test.go | 158 +-
tests/integration/api_user_avatar_test.go | 70 +-
tests/integration/api_user_block_test.go | 241 ++-
tests/integration/api_user_email_test.go | 159 +-
tests/integration/api_user_follow_test.go | 66 +-
tests/integration/api_user_orgs_test.go | 305 ++--
tests/integration/api_user_search_test.go | 243 ++-
tests/integration/api_user_star_test.go | 356 ++---
tests/integration/api_user_update_test.go | 25 -
tests/integration/api_user_variables_test.go | 11 +
tests/integration/api_user_watch_test.go | 86 +-
tests/integration/api_wiki_test.go | 319 ++--
tests/integration/attachment_test.go | 188 ++-
tests/integration/auth_oauth2_test.go | 878 ++++++-----
.../integration/change_default_branch_test.go | 130 +-
tests/integration/compare_test.go | 445 +++---
tests/integration/delete_user_test.go | 38 +-
tests/integration/download_test.go | 250 ++--
tests/integration/explore_repos_test.go | 25 -
tests/integration/explore_user_test.go | 60 +-
tests/integration/feed_repo_test.go | 92 --
tests/integration/feed_user_test.go | 151 +-
tests/integration/git_misc_test.go | 291 ++--
tests/integration/git_push_test.go | 131 +-
tests/integration/goget_test.go | 209 ++-
tests/integration/heatmap_test.go | 31 +-
tests/integration/incoming_email_test.go | 65 +-
tests/integration/issue_test.go | 717 ++++-----
tests/integration/lfs_local_endpoint_test.go | 3 -
tests/integration/linguist_test.go | 126 +-
tests/integration/markup_external_test.go | 2 -
tests/integration/migrate_test.go | 37 +-
tests/integration/mirror_pull_test.go | 46 +-
tests/integration/mirror_push_test.go | 73 +-
tests/integration/oauth_avatar_test.go | 96 --
tests/integration/oauth_test.go | 148 +-
tests/integration/org_team_invite_test.go | 470 +++---
tests/integration/packages_service_test.go | 37 +-
tests/integration/privateactivity_test.go | 389 ++---
tests/integration/project_test.go | 72 +-
tests/integration/project_workflow_test.go | 60 +-
tests/integration/pull_commit_test.go | 40 -
tests/integration/pull_compare_test.go | 27 +
tests/integration/pull_review_test.go | 74 +-
tests/integration/release_test.go | 30 +-
tests/integration/repo_activity_test.go | 27 +-
tests/integration/repo_commits_test.go | 100 +-
tests/integration/repo_merge_upstream_test.go | 50 +-
tests/integration/repo_topic_test.go | 81 +-
tests/integration/repo_webhook_test.go | 242 ++-
tests/integration/repofiles_change_test.go | 173 +--
tests/integration/setting_test.go | 69 +-
tests/integration/signin_test.go | 117 +-
tests/integration/signout_test.go | 45 -
.../user_settings_oauth2_grant_test.go | 27 -
tests/integration/user_settings_test.go | 336 ++---
tests/integration/version_test.go | 26 -
tests/integration/view_test.go | 11 +
388 files changed, 18410 insertions(+), 29561 deletions(-)
delete mode 100644 models/repo/archive_test.go
delete mode 100644 modules/git/blame_sha256_test.go
delete mode 100644 services/oauth2_provider/additional_scopes_test.go
delete mode 100644 tests/integration/api_admin_packages_test.go
delete mode 100644 tests/integration/api_issue_templates_test.go
delete mode 100644 tests/integration/api_license_templates_test.go
delete mode 100644 tests/integration/api_org_avatar_test.go
delete mode 100644 tests/integration/api_pull_commits_test.go
delete mode 100644 tests/integration/api_repo_delete_test.go
delete mode 100644 tests/integration/api_repo_git_notes_test.go
delete mode 100644 tests/integration/api_repo_git_ref_test.go
delete mode 100644 tests/integration/api_user_update_test.go
delete mode 100644 tests/integration/explore_repos_test.go
delete mode 100644 tests/integration/feed_repo_test.go
delete mode 100644 tests/integration/oauth_avatar_test.go
delete mode 100644 tests/integration/pull_commit_test.go
delete mode 100644 tests/integration/signout_test.go
delete mode 100644 tests/integration/user_settings_oauth2_grant_test.go
delete mode 100644 tests/integration/version_test.go
diff --git a/build/openapi3gen/convert_test.go b/build/openapi3gen/convert_test.go
index 1cf73bd1f0e..28d5413421d 100644
--- a/build/openapi3gen/convert_test.go
+++ b/build/openapi3gen/convert_test.go
@@ -42,27 +42,19 @@ func TestDeriveEnumName_miss(t *testing.T) {
func TestExtractSharedEnums_usesASTMap(t *testing.T) {
doc := &openapi3.T{
Components: &openapi3.Components{
- Schemas: openapi3.Schemas{
- "A": {Value: &openapi3.Schema{
- Type: &openapi3.Types{"object"},
- Properties: openapi3.Schemas{
- "color": {Value: &openapi3.Schema{
- Type: &openapi3.Types{"string"},
- Enum: []any{"red", "green", "blue"},
- }},
- },
- }},
- "B": {Value: &openapi3.Schema{
- Type: &openapi3.Types{"object"},
- Properties: openapi3.Schemas{
- "color": {Value: &openapi3.Schema{
- Type: &openapi3.Types{"string"},
- Enum: []any{"red", "green", "blue"},
- }},
- },
+ Schemas: openapi3.Schemas{},
+ },
+ }
+ for _, name := range []string{"A", "B"} {
+ doc.Components.Schemas[name] = &openapi3.SchemaRef{Value: &openapi3.Schema{
+ Type: &openapi3.Types{"object"},
+ Properties: openapi3.Schemas{
+ "color": {Value: &openapi3.Schema{
+ Type: &openapi3.Types{"string"},
+ Enum: []any{"red", "green", "blue"},
}},
},
- },
+ }}
}
astMap := map[string][]string{EnumKey([]any{"red", "green", "blue"}): {"Color"}}
if err := extractSharedEnums(doc, astMap); err != nil {
@@ -190,27 +182,19 @@ func TestExtractEnumTypeName_rejectsIncidentalPrefix(t *testing.T) {
func TestExtractSharedEnums_missReturnsError(t *testing.T) {
doc := &openapi3.T{
Components: &openapi3.Components{
- Schemas: openapi3.Schemas{
- "A": {Value: &openapi3.Schema{
- Type: &openapi3.Types{"object"},
- Properties: openapi3.Schemas{
- "color": {Value: &openapi3.Schema{
- Type: &openapi3.Types{"string"},
- Enum: []any{"red", "green"},
- }},
- },
- }},
- "B": {Value: &openapi3.Schema{
- Type: &openapi3.Types{"object"},
- Properties: openapi3.Schemas{
- "color": {Value: &openapi3.Schema{
- Type: &openapi3.Types{"string"},
- Enum: []any{"red", "green"},
- }},
- },
+ Schemas: openapi3.Schemas{},
+ },
+ }
+ for _, name := range []string{"A", "B"} {
+ doc.Components.Schemas[name] = &openapi3.SchemaRef{Value: &openapi3.Schema{
+ Type: &openapi3.Types{"object"},
+ Properties: openapi3.Schemas{
+ "color": {Value: &openapi3.Schema{
+ Type: &openapi3.Types{"string"},
+ Enum: []any{"red", "green"},
}},
},
- },
+ }}
}
if err := extractSharedEnums(doc, map[string][]string{}); err == nil {
t.Fatal("expected miss error")
diff --git a/build/openapi3gen/enumscan_test.go b/build/openapi3gen/enumscan_test.go
index 8cb16b721d1..439a3251746 100644
--- a/build/openapi3gen/enumscan_test.go
+++ b/build/openapi3gen/enumscan_test.go
@@ -33,9 +33,17 @@ func TestEnumKey_handlesNonStringValues(t *testing.T) {
}
}
-func TestScanSwaggerEnumTypes_basic(t *testing.T) {
- dir := t.TempDir()
- src := `package fixture
+func TestScanSwaggerEnumTypes(t *testing.T) {
+ scan := func(t *testing.T, filename, src string) (map[string][]string, error) {
+ dir := t.TempDir()
+ if err := os.WriteFile(filepath.Join(dir, filename), []byte(src), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ return ScanSwaggerEnumTypes([]string{dir})
+ }
+
+ t.Run("basic", func(t *testing.T) {
+ got, err := scan(t, "color.go", `package fixture
// Color is a primary color.
// swagger:enum Color
@@ -46,24 +54,18 @@ const (
ColorGreen Color = "green"
ColorBlue Color = "blue"
)
-`
- if err := os.WriteFile(filepath.Join(dir, "color.go"), []byte(src), 0o644); err != nil {
- t.Fatal(err)
- }
+`)
+ if err != nil {
+ t.Fatalf("ScanSwaggerEnumTypes: %v", err)
+ }
+ wantKey := EnumKey([]any{"red", "green", "blue"})
+ if single(got, wantKey) != "Color" {
+ t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
+ }
+ })
- got, err := ScanSwaggerEnumTypes([]string{dir})
- if err != nil {
- t.Fatalf("ScanSwaggerEnumTypes: %v", err)
- }
- wantKey := EnumKey([]any{"red", "green", "blue"})
- if single(got, wantKey) != "Color" {
- t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
- }
-}
-
-func TestScanSwaggerEnumTypes_orphanAnnotation(t *testing.T) {
- dir := t.TempDir()
- src := `package fixture
+ t.Run("orphanAnnotation", func(t *testing.T) {
+ _, err := scan(t, "typo.go", `package fixture
// swagger:enum Sttype
type StateType string
@@ -71,23 +73,17 @@ type StateType string
const (
StateOpen StateType = "open"
)
-`
- if err := os.WriteFile(filepath.Join(dir, "typo.go"), []byte(src), 0o644); err != nil {
- t.Fatal(err)
- }
+`)
+ if err == nil {
+ t.Fatal("expected error for annotation referencing a non-matching type name")
+ }
+ if !strings.Contains(err.Error(), "Sttype") {
+ t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
+ }
+ })
- _, err := ScanSwaggerEnumTypes([]string{dir})
- if err == nil {
- t.Fatal("expected error for annotation referencing a non-matching type name")
- }
- if !strings.Contains(err.Error(), "Sttype") {
- t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
- }
-}
-
-func TestScanSwaggerEnumTypes_collision(t *testing.T) {
- dir := t.TempDir()
- src := `package fixture
+ t.Run("collision", func(t *testing.T) {
+ got, err := scan(t, "dup.go", `package fixture
// swagger:enum Alpha
type Alpha string
@@ -102,52 +98,90 @@ const (
BetaX Beta = "x"
BetaY Beta = "y"
)
-`
- if err := os.WriteFile(filepath.Join(dir, "dup.go"), []byte(src), 0o644); err != nil {
- t.Fatal(err)
- }
+`)
+ if err != nil {
+ t.Fatalf("ScanSwaggerEnumTypes: %v", err)
+ }
+ key := EnumKey([]any{"x", "y"})
+ names := got[key]
+ if !slices.Equal(names, []string{"Alpha", "Beta"}) {
+ t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names)
+ }
+ })
- got, err := ScanSwaggerEnumTypes([]string{dir})
- if err != nil {
- t.Fatalf("ScanSwaggerEnumTypes: %v", err)
- }
- key := EnumKey([]any{"x", "y"})
- names := got[key]
- if !slices.Equal(names, []string{"Alpha", "Beta"}) {
- t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names)
- }
-}
+ t.Run("parseFailure", func(t *testing.T) {
+ _, err := scan(t, "bad.go", "package fixture\nfunc Foo() {")
+ if err == nil {
+ t.Fatal("expected parse error, got nil")
+ }
+ })
-func TestScanSwaggerEnumTypes_parseFailure(t *testing.T) {
- dir := t.TempDir()
- if err := os.WriteFile(filepath.Join(dir, "bad.go"), []byte("package fixture\nfunc Foo() {"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- _, err := ScanSwaggerEnumTypes([]string{dir})
- if err == nil {
- t.Fatal("expected parse error, got nil")
- }
-}
-
-func TestScanSwaggerEnumTypes_annotationWithoutConsts(t *testing.T) {
- dir := t.TempDir()
- src := `package fixture
+ t.Run("annotationWithoutConsts", func(t *testing.T) {
+ _, err := scan(t, "lonely.go", `package fixture
// swagger:enum Lonely
type Lonely string
-`
- if err := os.WriteFile(filepath.Join(dir, "lonely.go"), []byte(src), 0o644); err != nil {
- t.Fatal(err)
- }
+`)
+ if err == nil {
+ t.Fatal("expected error for annotation without consts")
+ }
+ if !strings.Contains(err.Error(), "Lonely") {
+ t.Fatalf("error %q should mention Lonely", err.Error())
+ }
+ })
- _, err := ScanSwaggerEnumTypes([]string{dir})
- if err == nil {
- t.Fatal("expected error for annotation without consts")
- }
- if !strings.Contains(err.Error(), "Lonely") {
- t.Fatalf("error %q should mention Lonely", err.Error())
- }
+ t.Run("constsBeforeType", func(t *testing.T) {
+ got, err := scan(t, "shade.go", `package fixture
+
+const (
+ ShadeDark Shade = "dark"
+ ShadeLight Shade = "light"
+)
+
+// swagger:enum Shade
+type Shade string
+`)
+ if err != nil {
+ t.Fatalf("ScanSwaggerEnumTypes: %v", err)
+ }
+ wantKey := EnumKey([]any{"dark", "light"})
+ if single(got, wantKey) != "Shade" {
+ t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
+ }
+ })
+
+ t.Run("groupedTypeDecl", func(t *testing.T) {
+ got, err := scan(t, "grouped.go", `package fixture
+
+type (
+ // swagger:enum Color
+ Color string
+ // swagger:enum Shade
+ Shade string
+)
+
+const (
+ ColorRed Color = "red"
+ ColorBlue Color = "blue"
+)
+
+const (
+ ShadeDark Shade = "dark"
+ ShadeLight Shade = "light"
+)
+`)
+ if err != nil {
+ t.Fatalf("ScanSwaggerEnumTypes: %v", err)
+ }
+ colorKey := EnumKey([]any{"red", "blue"})
+ shadeKey := EnumKey([]any{"dark", "light"})
+ if single(got, colorKey) != "Color" {
+ t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
+ }
+ if single(got, shadeKey) != "Shade" {
+ t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
+ }
+ })
}
func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) {
@@ -182,68 +216,3 @@ type Hue string
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue")
}
}
-
-func TestScanSwaggerEnumTypes_constsBeforeType(t *testing.T) {
- dir := t.TempDir()
- src := `package fixture
-
-const (
- ShadeDark Shade = "dark"
- ShadeLight Shade = "light"
-)
-
-// swagger:enum Shade
-type Shade string
-`
- if err := os.WriteFile(filepath.Join(dir, "shade.go"), []byte(src), 0o644); err != nil {
- t.Fatal(err)
- }
-
- got, err := ScanSwaggerEnumTypes([]string{dir})
- if err != nil {
- t.Fatalf("ScanSwaggerEnumTypes: %v", err)
- }
- wantKey := EnumKey([]any{"dark", "light"})
- if single(got, wantKey) != "Shade" {
- t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
- }
-}
-
-func TestScanSwaggerEnumTypes_groupedTypeDecl(t *testing.T) {
- dir := t.TempDir()
- src := `package fixture
-
-type (
- // swagger:enum Color
- Color string
- // swagger:enum Shade
- Shade string
-)
-
-const (
- ColorRed Color = "red"
- ColorBlue Color = "blue"
-)
-
-const (
- ShadeDark Shade = "dark"
- ShadeLight Shade = "light"
-)
-`
- if err := os.WriteFile(filepath.Join(dir, "grouped.go"), []byte(src), 0o644); err != nil {
- t.Fatal(err)
- }
-
- got, err := ScanSwaggerEnumTypes([]string{dir})
- if err != nil {
- t.Fatalf("ScanSwaggerEnumTypes: %v", err)
- }
- colorKey := EnumKey([]any{"red", "blue"})
- shadeKey := EnumKey([]any{"dark", "light"})
- if single(got, colorKey) != "Color" {
- t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
- }
- if single(got, shadeKey) != "Shade" {
- t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
- }
-}
diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go
index f9b5338ba0e..6614502eab7 100644
--- a/cmd/admin_auth_ldap_test.go
+++ b/cmd/admin_auth_ldap_test.go
@@ -489,13 +489,20 @@ func TestUpdateLdapBindDn(t *testing.T) {
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
// Test cases
- cases := []struct {
+ type testCase struct {
args []string
id int64
existingAuthSource *auth.Source
authSource *auth.Source
errMsg string
- }{
+ }
+ withConfig := func(config *ldap.Source, args ...string) testCase {
+ return testCase{
+ args: append([]string{"ldap-test", "--id", "1"}, args...),
+ authSource: &auth.Source{Type: auth.LDAP, Cfg: config},
+ }
+ }
+ cases := []testCase{
// case 0
{
args: []string{
@@ -574,16 +581,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
},
// case 1
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{},
- },
- },
+ withConfig(&ldap.Source{}),
// case 2
{
args: []string{
@@ -618,215 +616,44 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
},
// case 4
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--security-protocol", "LDAPS",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- SecurityProtocol: ldap.SecurityProtocol(1),
- },
- },
- },
+ withConfig(&ldap.Source{SecurityProtocol: ldap.SecurityProtocol(1)}, "--security-protocol", "LDAPS"),
// case 5
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--skip-tls-verify",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- SkipVerify: true,
- },
- },
- },
+ withConfig(&ldap.Source{SkipVerify: true}, "--skip-tls-verify"),
// case 6
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--host", "ldap-server",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- Host: "ldap-server",
- },
- },
- },
+ withConfig(&ldap.Source{Host: "ldap-server"}, "--host", "ldap-server"),
// case 7
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--port", "389",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- Port: 389,
- },
- },
- },
+ withConfig(&ldap.Source{Port: 389}, "--port", "389"),
// case 8
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--user-search-base", "ou=Users,dc=domain,dc=org",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- UserBase: "ou=Users,dc=domain,dc=org",
- },
- },
- },
+ withConfig(
+ &ldap.Source{UserBase: "ou=Users,dc=domain,dc=org"},
+ "--user-search-base", "ou=Users,dc=domain,dc=org",
+ ),
// case 9
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
- },
- },
- },
+ withConfig(
+ &ldap.Source{Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)"},
+ "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
+ ),
// case 10
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
- },
- },
- },
+ withConfig(
+ &ldap.Source{AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)"},
+ "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
+ ),
// case 11
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--username-attribute", "uid",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- AttributeUsername: "uid",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeUsername: "uid"}, "--username-attribute", "uid"),
// case 12
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--firstname-attribute", "givenName",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- AttributeName: "givenName",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeName: "givenName"}, "--firstname-attribute", "givenName"),
// case 13
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--surname-attribute", "sn",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- AttributeSurname: "sn",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeSurname: "sn"}, "--surname-attribute", "sn"),
// case 14
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--email-attribute", "mail",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- AttributeMail: "mail",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeMail: "mail"}, "--email-attribute", "mail"),
// case 15
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--attributes-in-bind",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- AttributesInBind: true,
- },
- },
- },
+ withConfig(&ldap.Source{AttributesInBind: true}, "--attributes-in-bind"),
// case 16
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--public-ssh-key-attribute", "publickey",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- AttributeSSHPublicKey: "publickey",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeSSHPublicKey: "publickey"}, "--public-ssh-key-attribute", "publickey"),
// case 17
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--bind-dn", "cn=readonly,dc=domain,dc=org",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- BindDN: "cn=readonly,dc=domain,dc=org",
- },
- },
- },
+ withConfig(&ldap.Source{BindDN: "cn=readonly,dc=domain,dc=org"}, "--bind-dn", "cn=readonly,dc=domain,dc=org"),
// case 18
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--bind-password", "secret",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- BindPassword: "secret",
- },
- },
- },
+ withConfig(&ldap.Source{BindPassword: "secret"}, "--bind-password", "secret"),
// case 19
{
args: []string{
@@ -841,19 +668,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
},
// case 20
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--page-size", "12",
- },
- authSource: &auth.Source{
- Type: auth.LDAP,
- Cfg: &ldap.Source{
- SearchPageSize: 12,
- },
- },
- },
+ withConfig(&ldap.Source{SearchPageSize: 12}, "--page-size", "12"),
// case 21
{
args: []string{
@@ -964,13 +779,20 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
// Test cases
- cases := []struct {
+ type testCase struct {
args []string
id int64
existingAuthSource *auth.Source
authSource *auth.Source
errMsg string
- }{
+ }
+ withConfig := func(config *ldap.Source, args ...string) testCase {
+ return testCase{
+ args: append([]string{"ldap-test", "--id", "1"}, args...),
+ authSource: &auth.Source{Type: auth.DLDAP, Cfg: config},
+ }
+ }
+ cases := []testCase{
// case 0
{
args: []string{
@@ -1020,16 +842,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
},
// case 1
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{},
- },
- },
+ withConfig(&ldap.Source{}),
// case 2
{
args: []string{
@@ -1064,187 +877,43 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
},
// case 4
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--security-protocol", "starttls",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- SecurityProtocol: ldap.SecurityProtocol(2),
- },
- },
- },
+ withConfig(&ldap.Source{SecurityProtocol: ldap.SecurityProtocol(2)}, "--security-protocol", "starttls"),
// case 5
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--skip-tls-verify",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- SkipVerify: true,
- },
- },
- },
+ withConfig(&ldap.Source{SkipVerify: true}, "--skip-tls-verify"),
// case 6
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--host", "ldap-server",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- Host: "ldap-server",
- },
- },
- },
+ withConfig(&ldap.Source{Host: "ldap-server"}, "--host", "ldap-server"),
// case 7
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--port", "987",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- Port: 987,
- },
- },
- },
+ withConfig(&ldap.Source{Port: 987}, "--port", "987"),
// case 8
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--user-search-base", "ou=Users,dc=domain,dc=org",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- UserBase: "ou=Users,dc=domain,dc=org",
- },
- },
- },
+ withConfig(
+ &ldap.Source{UserBase: "ou=Users,dc=domain,dc=org"},
+ "--user-search-base", "ou=Users,dc=domain,dc=org",
+ ),
// case 9
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- Filter: "(&(objectClass=posixAccount)(cn=%s))",
- },
- },
- },
+ withConfig(
+ &ldap.Source{Filter: "(&(objectClass=posixAccount)(cn=%s))"},
+ "--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
+ ),
// case 10
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
- },
- },
- },
+ withConfig(
+ &ldap.Source{AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)"},
+ "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
+ ),
// case 11
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--username-attribute", "uid",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- AttributeUsername: "uid",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeUsername: "uid"}, "--username-attribute", "uid"),
// case 12
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--firstname-attribute", "givenName",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- AttributeName: "givenName",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeName: "givenName"}, "--firstname-attribute", "givenName"),
// case 13
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--surname-attribute", "sn",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- AttributeSurname: "sn",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeSurname: "sn"}, "--surname-attribute", "sn"),
// case 14
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--email-attribute", "mail",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- AttributeMail: "mail",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeMail: "mail"}, "--email-attribute", "mail"),
// case 15
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--public-ssh-key-attribute", "publickey",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- AttributeSSHPublicKey: "publickey",
- },
- },
- },
+ withConfig(&ldap.Source{AttributeSSHPublicKey: "publickey"}, "--public-ssh-key-attribute", "publickey"),
// case 16
- {
- args: []string{
- "ldap-test",
- "--id", "1",
- "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
- },
- authSource: &auth.Source{
- Type: auth.DLDAP,
- Cfg: &ldap.Source{
- UserDN: "cn=%s,ou=Users,dc=domain,dc=org",
- },
- },
- },
+ withConfig(
+ &ldap.Source{UserDN: "cn=%s,ou=Users,dc=domain,dc=org"},
+ "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
+ ),
// case 17
{
args: []string{
diff --git a/cmd/admin_auth_oauth_test.go b/cmd/admin_auth_oauth_test.go
index c16dd96d565..332e30243f0 100644
--- a/cmd/admin_auth_oauth_test.go
+++ b/cmd/admin_auth_oauth_test.go
@@ -16,10 +16,11 @@ import (
func TestAddOauth(t *testing.T) {
testCases := []struct {
- name string
- args []string
- source *auth_model.Source
- errMsg string
+ name string
+ args []string
+ config *oauth2.Source
+ twoFactorPolicy string
+ errMsg string
}{
{
name: "valid config",
@@ -29,17 +30,11 @@ func TestAddOauth(t *testing.T) {
"--key", "some_key",
"--secret", "some_secret",
},
- source: &auth_model.Source{
- Type: auth_model.OAuth2,
- Name: "test",
- IsActive: true,
- Cfg: &oauth2.Source{
- Scopes: []string{},
- Provider: "github",
- ClientID: "some_key",
- ClientSecret: "some_secret",
- },
- TwoFactorPolicy: "",
+ config: &oauth2.Source{
+ Scopes: []string{},
+ Provider: "github",
+ ClientID: "some_key",
+ ClientSecret: "some_secret",
},
},
{
@@ -51,18 +46,12 @@ func TestAddOauth(t *testing.T) {
"--secret", "some_secret",
"--auto-discover-url", "https://example.com",
},
- source: &auth_model.Source{
- Type: auth_model.OAuth2,
- Name: "test",
- IsActive: true,
- Cfg: &oauth2.Source{
- Scopes: []string{},
- Provider: "openidConnect",
- ClientID: "some_key",
- ClientSecret: "some_secret",
- OpenIDConnectAutoDiscoveryURL: "https://example.com",
- },
- TwoFactorPolicy: "",
+ config: &oauth2.Source{
+ Scopes: []string{},
+ Provider: "openidConnect",
+ ClientID: "some_key",
+ ClientSecret: "some_secret",
+ OpenIDConnectAutoDiscoveryURL: "https://example.com",
},
},
{
@@ -91,35 +80,30 @@ func TestAddOauth(t *testing.T) {
"--ssh-public-key-claim-name", "attr_ssh_pub_key",
"--full-name-claim-name", "attr_full_name",
},
- source: &auth_model.Source{
- Type: auth_model.OAuth2,
- Name: "test",
- IsActive: true,
- Cfg: &oauth2.Source{
- Provider: "gitlab",
- ClientID: "some_key",
- ClientSecret: "some_secret",
- CustomURLMapping: &oauth2.CustomURLMapping{
- TokenURL: "https://example.com/token",
- AuthURL: "https://example.com/auth",
- ProfileURL: "https://example.com/profile",
- EmailURL: "https://example.com/email",
- Tenant: "some_tenant",
- },
- IconURL: "https://example.com/icon",
- Scopes: []string{"scope1", "scope2"},
- RequiredClaimName: "claim_name",
- RequiredClaimValue: "claim_value",
- GroupClaimName: "group_name",
- AdminGroup: "admin",
- RestrictedGroup: "restricted",
- GroupTeamMap: `{"group1": [1,2]}`,
- GroupTeamMapRemoval: true,
- SSHPublicKeyClaimName: "attr_ssh_pub_key",
- FullNameClaimName: "attr_full_name",
+ config: &oauth2.Source{
+ Provider: "gitlab",
+ ClientID: "some_key",
+ ClientSecret: "some_secret",
+ CustomURLMapping: &oauth2.CustomURLMapping{
+ TokenURL: "https://example.com/token",
+ AuthURL: "https://example.com/auth",
+ ProfileURL: "https://example.com/profile",
+ EmailURL: "https://example.com/email",
+ Tenant: "some_tenant",
},
- TwoFactorPolicy: "skip",
+ IconURL: "https://example.com/icon",
+ Scopes: []string{"scope1", "scope2"},
+ RequiredClaimName: "claim_name",
+ RequiredClaimValue: "claim_value",
+ GroupClaimName: "group_name",
+ AdminGroup: "admin",
+ RestrictedGroup: "restricted",
+ GroupTeamMap: `{"group1": [1,2]}`,
+ GroupTeamMapRemoval: true,
+ SSHPublicKeyClaimName: "attr_ssh_pub_key",
+ FullNameClaimName: "attr_full_name",
},
+ twoFactorPolicy: "skip",
},
}
@@ -150,7 +134,13 @@ func TestAddOauth(t *testing.T) {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
- assert.Equal(t, tc.source, createdSource)
+ assert.Equal(t, &auth_model.Source{
+ Type: auth_model.OAuth2,
+ Name: "test",
+ IsActive: true,
+ Cfg: tc.config,
+ TwoFactorPolicy: tc.twoFactorPolicy,
+ }, createdSource)
}
})
}
@@ -158,12 +148,10 @@ func TestAddOauth(t *testing.T) {
func TestUpdateOauth(t *testing.T) {
testCases := []struct {
- name string
- args []string
- id int64
- existingAuthSource *auth_model.Source
- authSource *auth_model.Source
- errMsg string
+ name string
+ args []string
+ authSource *auth_model.Source
+ errMsg string
}{
{
name: "missing id",
@@ -174,19 +162,6 @@ func TestUpdateOauth(t *testing.T) {
},
{
name: "valid config",
- id: 1,
- existingAuthSource: &auth_model.Source{
- ID: 1,
- Type: auth_model.OAuth2,
- Name: "old name",
- IsActive: true,
- Cfg: &oauth2.Source{
- Provider: "github",
- ClientID: "old_key",
- ClientSecret: "old_secret",
- },
- TwoFactorPolicy: "",
- },
args: []string{
"--id", "1",
"--name", "test",
@@ -210,37 +185,6 @@ func TestUpdateOauth(t *testing.T) {
},
{
name: "valid config with options",
- id: 1,
- existingAuthSource: &auth_model.Source{
- ID: 1,
- Type: auth_model.OAuth2,
- Name: "old name",
- IsActive: true,
- Cfg: &oauth2.Source{
- Provider: "gitlab",
- ClientID: "old_key",
- ClientSecret: "old_secret",
- CustomURLMapping: &oauth2.CustomURLMapping{
- TokenURL: "https://old.example.com/token",
- AuthURL: "https://old.example.com/auth",
- ProfileURL: "https://old.example.com/profile",
- EmailURL: "https://old.example.com/email",
- Tenant: "old_tenant",
- },
- IconURL: "https://old.example.com/icon",
- Scopes: []string{"old_scope1", "old_scope2"},
- RequiredClaimName: "old_claim_name",
- RequiredClaimValue: "old_claim_value",
- GroupClaimName: "old_group_name",
- AdminGroup: "old_admin",
- RestrictedGroup: "old_restricted",
- GroupTeamMap: `{"old_group1": [1,2]}`,
- GroupTeamMapRemoval: true,
- SSHPublicKeyClaimName: "old_ssh_pub_key",
- FullNameClaimName: "old_full_name",
- },
- TwoFactorPolicy: "",
- },
args: []string{
"--id", "1",
"--name", "test",
diff --git a/cmd/admin_auth_smtp_test.go b/cmd/admin_auth_smtp_test.go
index 2b890f154aa..a1cb79e69aa 100644
--- a/cmd/admin_auth_smtp_test.go
+++ b/cmd/admin_auth_smtp_test.go
@@ -132,11 +132,10 @@ func TestAddSMTP(t *testing.T) {
func TestUpdateSMTP(t *testing.T) {
testCases := []struct {
- name string
- args []string
- existingAuthSource *auth_model.Source
- authSource *auth_model.Source
- errMsg string
+ name string
+ args []string
+ authSource *auth_model.Source
+ errMsg string
}{
{
name: "missing id",
@@ -149,17 +148,6 @@ func TestUpdateSMTP(t *testing.T) {
},
{
name: "valid config",
- existingAuthSource: &auth_model.Source{
- ID: 1,
- Type: auth_model.SMTP,
- Name: "old name",
- IsActive: true,
- Cfg: &smtp.Source{
- Auth: "PLAIN",
- Host: "old host",
- Port: 26,
- },
- },
args: []string{
"--id", "1",
"--name", "test",
@@ -180,20 +168,6 @@ func TestUpdateSMTP(t *testing.T) {
},
{
name: "valid config with options",
- existingAuthSource: &auth_model.Source{
- ID: 1,
- Type: auth_model.SMTP,
- Name: "old name",
- IsActive: true,
- Cfg: &smtp.Source{
- Auth: "PLAIN",
- Host: "old host",
- Port: 26,
- HeloHostname: "old.example.com",
- AllowedDomains: "old.example.com",
- },
- TwoFactorPolicy: "",
- },
args: []string{
"--id", "1",
"--name", "test",
diff --git a/cmd/admin_user_delete_test.go b/cmd/admin_user_delete_test.go
index 4fbd0435c51..6d61b010ff1 100644
--- a/cmd/admin_user_delete_test.go
+++ b/cmd/admin_user_delete_test.go
@@ -24,42 +24,29 @@ func TestAdminUserDelete(t *testing.T) {
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
}()
- setupTestUser := func(t *testing.T) {
- unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
- err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
- require.NoError(t, err)
+ for _, tc := range []struct {
+ name string
+ args []string
+ }{
+ {"delete user by id", []string{"delete-test", "--id", ""}},
+ {"delete user by username", []string{"delete-test", "--username", "testuser"}},
+ {"delete user by email", []string{"delete-test", "--email", "testuser@gitea.local"}},
+ {"delete user by all 3 attributes", []string{"delete", "--id", "", "--username", "testuser", "--email", "testuser@gitea.local"}},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
+ require.NoError(t, err)
+
+ if tc.args[1] == "--id" {
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ tc.args[2] = strconv.FormatInt(user.ID, 10)
+ }
+ err = microcmdUserDelete().Run(ctx, tc.args)
+ require.NoError(t, err)
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ })
}
-
- t.Run("delete user by id", func(t *testing.T) {
- setupTestUser(t)
-
- u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
- err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)})
- require.NoError(t, err)
- unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
- })
- t.Run("delete user by username", func(t *testing.T) {
- setupTestUser(t)
-
- err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"})
- require.NoError(t, err)
- unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
- })
- t.Run("delete user by email", func(t *testing.T) {
- setupTestUser(t)
-
- err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"})
- require.NoError(t, err)
- unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
- })
- t.Run("delete user by all 3 attributes", func(t *testing.T) {
- setupTestUser(t)
-
- u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
- err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"})
- require.NoError(t, err)
- unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
- })
}
func TestAdminUserDeleteFailure(t *testing.T) {
diff --git a/cmd/admin_user_must_change_password_test.go b/cmd/admin_user_must_change_password_test.go
index cf81fbe0ba7..f5544dca0fa 100644
--- a/cmd/admin_user_must_change_password_test.go
+++ b/cmd/admin_user_must_change_password_test.go
@@ -18,61 +18,28 @@ func TestMustChangePassword(t *testing.T) {
defer func() {
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
}()
- err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
- require.NoError(t, err)
- err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"})
- require.NoError(t, err)
- // Reset password change flag
- err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"})
- require.NoError(t, err)
-
- testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
- assert.False(t, testUser.MustChangePassword)
- testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
- assert.False(t, testUserExclude.MustChangePassword)
-
- // Make all users change password
- err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"})
- require.NoError(t, err)
-
- testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
- assert.True(t, testUser.MustChangePassword)
- testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
- assert.True(t, testUserExclude.MustChangePassword)
-
- // Reset password change flag but exclude all tested users
- err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"})
- require.NoError(t, err)
-
- testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
- assert.True(t, testUser.MustChangePassword)
- testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
- assert.True(t, testUserExclude.MustChangePassword)
-
- // Reset password change flag by listing multiple users
- err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"})
- require.NoError(t, err)
-
- testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
- assert.False(t, testUser.MustChangePassword)
- testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
- assert.False(t, testUserExclude.MustChangePassword)
-
- // Exclude a user from all user
- err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"})
- require.NoError(t, err)
-
- testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
- assert.True(t, testUser.MustChangePassword)
- testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
- assert.False(t, testUserExclude.MustChangePassword)
-
- // Unset a flag for single user
- err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"})
- require.NoError(t, err)
-
- testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
- assert.False(t, testUser.MustChangePassword)
- testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
- assert.False(t, testUserExclude.MustChangePassword)
+ for _, name := range []string{"testuser", "testuserexclude"} {
+ require.NoError(t, microcmdUserCreate().Run(t.Context(), []string{"create", "--username", name, "--email", name + "@gitea.local", "--random-password"}))
+ }
+ for _, tc := range []struct {
+ args []string
+ assertUser, assertExcludedUser assert.BoolAssertionFunc
+ }{
+ // Reset password change flag
+ {[]string{"change-test", "--all", "--unset"}, assert.False, assert.False},
+ // Make all users change password
+ {[]string{"change-test", "--all"}, assert.True, assert.True},
+ // Reset password change flag but exclude all tested users
+ {[]string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"}, assert.True, assert.True},
+ // Reset password change flag by listing multiple users
+ {[]string{"change-test", "--unset", "testuser", "testuserexclude"}, assert.False, assert.False},
+ // Exclude a user from all user
+ {[]string{"change-test", "--all", "--exclude", "testuserexclude"}, assert.True, assert.False},
+ // Unset a flag for single user
+ {[]string{"change-test", "--unset", "testuser"}, assert.False, assert.False},
+ } {
+ require.NoError(t, microcmdUserMustChangePassword().Run(t.Context(), tc.args))
+ tc.assertUser(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}).MustChangePassword)
+ tc.assertExcludedUser(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}).MustChangePassword)
+ }
}
diff --git a/cmd/cmdtest/cmd_test.go b/cmd/cmdtest/cmd_test.go
index f41793c8e44..557eef2aa1e 100644
--- a/cmd/cmdtest/cmd_test.go
+++ b/cmd/cmdtest/cmd_test.go
@@ -76,110 +76,82 @@ func TestCliCmd(t *testing.T) {
cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct {
- env map[string]string
- cmd string
- exp string
+ env map[string]string
+ cmds []string
+ exp string
}{
// help commands
{
- cmd: "./gitea -h",
- exp: "DEFAULT CONFIGURATION:",
+ cmds: []string{"./gitea -h", "./gitea help"},
+ exp: "DEFAULT CONFIGURATION:",
},
{
- cmd: "./gitea help",
- exp: "DEFAULT CONFIGURATION:",
- },
-
- {
- cmd: "./gitea -c /dev/null -h",
- exp: "ConfigFile: /dev/null",
- },
-
- {
- cmd: "./gitea -c /dev/null help",
- exp: "ConfigFile: /dev/null",
- },
- {
- cmd: "./gitea help -c /dev/null",
- exp: "ConfigFile: /dev/null",
- },
-
- {
- cmd: "./gitea -c /dev/null test-cmd -h",
- exp: "ConfigFile: /dev/null",
- },
- {
- cmd: "./gitea test-cmd -c /dev/null -h",
- exp: "ConfigFile: /dev/null",
- },
- {
- cmd: "./gitea test-cmd -h -c /dev/null",
- exp: "ConfigFile: /dev/null",
- },
-
- {
- cmd: "./gitea -c /dev/null test-cmd help",
- exp: "ConfigFile: /dev/null",
- },
- {
- cmd: "./gitea test-cmd -c /dev/null help",
- exp: "ConfigFile: /dev/null",
- },
- {
- cmd: "./gitea test-cmd help -c /dev/null",
+ cmds: []string{
+ "./gitea -c /dev/null -h",
+ "./gitea -c /dev/null help",
+ "./gitea help -c /dev/null",
+ "./gitea -c /dev/null test-cmd -h",
+ "./gitea test-cmd -c /dev/null -h",
+ "./gitea test-cmd -h -c /dev/null",
+ "./gitea -c /dev/null test-cmd help",
+ "./gitea test-cmd -c /dev/null help",
+ "./gitea test-cmd help -c /dev/null",
+ },
exp: "ConfigFile: /dev/null",
},
// parse paths
{
- cmd: "./gitea test-cmd",
- exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
+ cmds: []string{"./gitea test-cmd"},
+ exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
},
{
- cmd: "./gitea -c /tmp/app.ini test-cmd",
- exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
+ cmds: []string{"./gitea -c /tmp/app.ini test-cmd"},
+ exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
},
{
- cmd: "./gitea test-cmd -c /tmp/app.ini",
- exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
+ cmds: []string{"./gitea test-cmd -c /tmp/app.ini"},
+ exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
},
{
- env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
- cmd: "./gitea test-cmd",
- exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
+ env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
+ cmds: []string{"./gitea test-cmd"},
+ exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
},
{
- env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
- cmd: "./gitea test-cmd --work-path /tmp/other",
- exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
+ env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
+ cmds: []string{"./gitea test-cmd --work-path /tmp/other"},
+ exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
},
{
- env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
- cmd: "./gitea test-cmd --config /tmp/app-other.ini",
- exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
+ env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
+ cmds: []string{"./gitea test-cmd --config /tmp/app-other.ini"},
+ exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
},
}
for _, c := range cases {
- t.Run(c.cmd, func(t *testing.T) {
- app := newTestApp(cli.Command{
- Action: func(ctx context.Context, cmd *cli.Command) error {
- _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
- return nil
- },
+ for _, command := range c.cmds {
+ t.Run(command, func(t *testing.T) {
+ app := newTestApp(cli.Command{
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
+ return nil
+ },
+ })
+ for k, v := range c.env {
+ t.Setenv(k, v)
+ }
+ args := strings.Split(command, " ") // for test only, "split" is good enough
+ r, err := runTestApp(app, args...)
+ assert.NoError(t, err, command)
+ assert.NotEmpty(t, c.exp, command)
+ if !assert.Contains(t, r.Stdout, c.exp, command) {
+ t.Log("Full output:\n" + r.Stdout)
+ t.Log("Expected:\n" + c.exp)
+ }
})
- for k, v := range c.env {
- t.Setenv(k, v)
- }
- args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
- r, err := runTestApp(app, args...)
- assert.NoError(t, err, c.cmd)
- assert.NotEmpty(t, c.exp, c.cmd)
- if !assert.Contains(t, r.Stdout, c.exp, c.cmd) {
- t.Log("Full output:\n" + r.Stdout)
- t.Log("Expected:\n" + c.exp)
- }
- })
+ }
}
}
diff --git a/modelmigration/base/db_test.go b/modelmigration/base/db_test.go
index eb5a212a76c..5a8a0984f92 100644
--- a/modelmigration/base/db_test.go
+++ b/modelmigration/base/db_test.go
@@ -45,38 +45,15 @@ func Test_DropTableColumns(t *testing.T) {
x.SetMapper(names.GonicMapper{})
for i := range columns {
- if err := x.Sync(new(DropTest)); err != nil {
- t.Errorf("unable to create DropTest table: %v", err)
- return
- }
-
- sess := x.NewSession()
- if err := sess.Begin(); err != nil {
- sess.Close()
- t.Errorf("unable to begin transaction: %v", err)
- return
- }
- if err := base.DropTableColumns(sess, "drop_test", columns[i:]...); err != nil {
- sess.Close()
- t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
- return
- }
- if err := sess.Commit(); err != nil {
- sess.Close()
- t.Errorf("unable to commit transaction: %v", err)
- return
- }
- sess.Close()
- if err := x.DropTables(new(DropTest)); err != nil {
- t.Errorf("unable to drop table: %v", err)
- return
- }
- for j := range columns[i+1:] {
+ for j := i; j < len(columns); j++ {
if err := x.Sync(new(DropTest)); err != nil {
t.Errorf("unable to create DropTest table: %v", err)
return
}
- dropcols := append([]string{columns[i]}, columns[j+i+1:]...)
+ dropcols := columns[i:]
+ if j > i {
+ dropcols = append([]string{columns[i]}, columns[j:]...)
+ }
sess := x.NewSession()
if err := sess.Begin(); err != nil {
sess.Close()
@@ -85,7 +62,11 @@ func Test_DropTableColumns(t *testing.T) {
}
if err := base.DropTableColumns(sess, "drop_test", dropcols...); err != nil {
sess.Close()
- t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
+ if j == i {
+ t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
+ } else {
+ t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
+ }
return
}
if err := sess.Commit(); err != nil {
diff --git a/modelmigration/v1_20/v259_test.go b/modelmigration/v1_20/v259_test.go
index a3f3ee78c19..b657c51fdd5 100644
--- a/modelmigration/v1_20/v259_test.go
+++ b/modelmigration/v1_20/v259_test.go
@@ -78,12 +78,17 @@ func Test_ConvertScopedAccessTokens(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
+ session := x.NewSession()
+ defer session.Close()
+ assert.NoError(t, session.Begin())
+
for _, tc := range tests {
- _, err = x.Insert(&AccessToken{
+ _, err = session.Insert(&AccessToken{
Scope: string(tc.Old),
})
assert.NoError(t, err)
}
+ assert.NoError(t, session.Commit())
// migrate the scopes
err = ConvertScopedAccessTokens(t.Context(), x)
diff --git a/modelmigration/v1_26/v329_test.go b/modelmigration/v1_26/v329_test.go
index d627030ebe2..0083c9ab5c7 100644
--- a/modelmigration/v1_26/v329_test.go
+++ b/modelmigration/v1_26/v329_test.go
@@ -43,13 +43,11 @@ func Test_AddUniqueIndexForUserBadge(t *testing.T) {
}
// check that we have duplicates
- count, err := x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
- assert.NoError(t, err)
- assert.Equal(t, int64(2), count)
-
- count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
- assert.NoError(t, err)
- assert.Equal(t, int64(2), count)
+ for _, userID := range []int{1, 3} {
+ count, err := x.Where("user_id = ? AND badge_id = ?", userID, userID).Count(&UserBadgeBefore{})
+ assert.NoError(t, err)
+ assert.Equal(t, int64(2), count)
+ }
totalCount, err := x.Count(&UserBadgeBefore{})
assert.NoError(t, err)
@@ -62,13 +60,11 @@ func Test_AddUniqueIndexForUserBadge(t *testing.T) {
}
// verify the duplicates were removed
- count, err = x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
- assert.NoError(t, err)
- assert.Equal(t, int64(1), count)
-
- count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
- assert.NoError(t, err)
- assert.Equal(t, int64(1), count)
+ for _, userID := range []int{1, 3} {
+ count, err := x.Where("user_id = ? AND badge_id = ?", userID, userID).Count(&UserBadgeBefore{})
+ assert.NoError(t, err)
+ assert.Equal(t, int64(1), count)
+ }
// check total count
totalCount, err = x.Count(&UserBadgeBefore{})
diff --git a/models/actions/run_job_status_test.go b/models/actions/run_job_status_test.go
index 90cb4afd436..738fe300a0b 100644
--- a/models/actions/run_job_status_test.go
+++ b/models/actions/run_job_status_test.go
@@ -10,22 +10,6 @@ import (
)
func TestAggregateJobStatus(t *testing.T) {
- testStatuses := func(expected Status, statuses []Status) {
- t.Helper()
- var jobs []*ActionRunJob
- for _, v := range statuses {
- jobs = append(jobs, &ActionRunJob{Status: v})
- }
- actual := AggregateJobStatus(jobs)
- if !assert.Equal(t, expected, actual) {
- var statusStrings []string
- for _, s := range statuses {
- statusStrings = append(statusStrings, s.String())
- }
- t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[expected])
- }
- }
-
cases := []struct {
statuses []Status
expected Status
@@ -92,6 +76,17 @@ func TestAggregateJobStatus(t *testing.T) {
}
for _, c := range cases {
- testStatuses(c.expected, c.statuses)
+ var jobs []*ActionRunJob
+ for _, status := range c.statuses {
+ jobs = append(jobs, &ActionRunJob{Status: status})
+ }
+ actual := AggregateJobStatus(jobs)
+ if !assert.Equal(t, c.expected, actual) {
+ var statusStrings []string
+ for _, status := range c.statuses {
+ statusStrings = append(statusStrings, status.String())
+ }
+ t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[c.expected])
+ }
}
}
diff --git a/models/actions/run_job_test.go b/models/actions/run_job_test.go
index 9ab0babee24..743d7de479e 100644
--- a/models/actions/run_job_test.go
+++ b/models/actions/run_job_test.go
@@ -16,189 +16,267 @@ import (
"github.com/stretchr/testify/require"
)
-func TestGetPriorAttemptChildrenByParent(t *testing.T) {
+func TestRunJobDatabase(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
- ctx := t.Context()
- // 3 attempts of one run:
- // 1: caller expanded with 3 matrix instances of "work" + non-matrix sibling "summary".
- // 2: caller skipped, no children rows.
- // 3: placeholder "current" attempt for the walkback subtest.
+ t.Run("prior attempt children by parent", func(t *testing.T) {
+ ctx := t.Context()
- run := &ActionRun{
- Title: "prior-children-test",
- RepoID: 4,
- Index: 9501,
- OwnerID: 1,
- WorkflowID: "matrix.yaml",
- TriggerUserID: 1,
- Ref: "refs/heads/master",
- CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
- Event: "push",
- TriggerEvent: "push",
- EventPayload: "{}",
- Status: StatusSuccess,
- }
- require.NoError(t, db.Insert(ctx, run))
+ // 3 attempts of one run:
+ // 1: caller expanded with 3 matrix instances of "work" + non-matrix sibling "summary".
+ // 2: caller skipped, no children rows.
+ // 3: placeholder "current" attempt for the walkback subtest.
- const callerAttemptJobID int64 = 9001
- insertAttempt := func(t *testing.T, num int64, status Status) *ActionRunAttempt {
- t.Helper()
- a := &ActionRunAttempt{
- RepoID: run.RepoID,
- RunID: run.ID,
- Attempt: num,
+ run := &ActionRun{
+ Title: "prior-children-test",
+ RepoID: 4,
+ Index: 9501,
+ OwnerID: 1,
+ WorkflowID: "matrix.yaml",
TriggerUserID: 1,
- Status: status,
+ Ref: "refs/heads/master",
+ CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
+ Event: "push",
+ TriggerEvent: "push",
+ EventPayload: "{}",
+ Status: StatusSuccess,
}
- require.NoError(t, db.Insert(ctx, a))
- return a
- }
- insertCaller := func(t *testing.T, attemptID int64, status Status, expanded bool) *ActionRunJob {
- t.Helper()
- caller := &ActionRunJob{
- RunID: run.ID,
- RunAttemptID: attemptID,
- RepoID: run.RepoID,
- OwnerID: run.OwnerID,
- CommitSHA: run.CommitSHA,
- Name: "caller",
- JobID: "caller",
- Attempt: 1,
- Status: status,
- AttemptJobID: callerAttemptJobID,
- IsReusableCaller: true,
- IsExpanded: expanded,
+ require.NoError(t, db.Insert(ctx, run))
+
+ const callerAttemptJobID int64 = 9001
+ insertAttempt := func(t *testing.T, num int64, status Status) *ActionRunAttempt {
+ t.Helper()
+ a := &ActionRunAttempt{
+ RepoID: run.RepoID,
+ RunID: run.ID,
+ Attempt: num,
+ TriggerUserID: 1,
+ Status: status,
+ }
+ require.NoError(t, db.Insert(ctx, a))
+ return a
+ }
+ insertCaller := func(t *testing.T, attemptID int64, status Status, expanded bool) *ActionRunJob {
+ t.Helper()
+ caller := &ActionRunJob{
+ RunID: run.ID,
+ RunAttemptID: attemptID,
+ RepoID: run.RepoID,
+ OwnerID: run.OwnerID,
+ CommitSHA: run.CommitSHA,
+ Name: "caller",
+ JobID: "caller",
+ Attempt: 1,
+ Status: status,
+ AttemptJobID: callerAttemptJobID,
+ IsReusableCaller: true,
+ IsExpanded: expanded,
+ }
+ require.NoError(t, db.Insert(ctx, caller))
+ return caller
+ }
+ insertChild := func(t *testing.T, attemptID, parentID, attemptJobID int64, name, jobID string) {
+ t.Helper()
+ require.NoError(t, db.Insert(ctx, &ActionRunJob{
+ RunID: run.ID,
+ RunAttemptID: attemptID,
+ RepoID: run.RepoID,
+ OwnerID: run.OwnerID,
+ CommitSHA: run.CommitSHA,
+ Name: name,
+ JobID: jobID,
+ Attempt: 1,
+ Status: StatusSuccess,
+ AttemptJobID: attemptJobID,
+ ParentJobID: parentID,
+ }))
}
- require.NoError(t, db.Insert(ctx, caller))
- return caller
- }
- insertChild := func(t *testing.T, attemptID, parentID, attemptJobID int64, name, jobID string) {
- t.Helper()
- require.NoError(t, db.Insert(ctx, &ActionRunJob{
- RunID: run.ID,
- RunAttemptID: attemptID,
- RepoID: run.RepoID,
- OwnerID: run.OwnerID,
- CommitSHA: run.CommitSHA,
- Name: name,
- JobID: jobID,
- Attempt: 1,
- Status: StatusSuccess,
- AttemptJobID: attemptJobID,
- ParentJobID: parentID,
- }))
- }
- attempt1 := insertAttempt(t, 1, StatusSuccess)
- caller1 := insertCaller(t, attempt1.ID, StatusSuccess, true)
- insertChild(t, attempt1.ID, caller1.ID, 101, "work (alpha)", "work")
- insertChild(t, attempt1.ID, caller1.ID, 102, "work (beta)", "work")
- insertChild(t, attempt1.ID, caller1.ID, 103, "work (gamma)", "work")
- insertChild(t, attempt1.ID, caller1.ID, 104, "summary", "summary")
+ attempt1 := insertAttempt(t, 1, StatusSuccess)
+ caller1 := insertCaller(t, attempt1.ID, StatusSuccess, true)
+ insertChild(t, attempt1.ID, caller1.ID, 101, "work (alpha)", "work")
+ insertChild(t, attempt1.ID, caller1.ID, 102, "work (beta)", "work")
+ insertChild(t, attempt1.ID, caller1.ID, 103, "work (gamma)", "work")
+ insertChild(t, attempt1.ID, caller1.ID, 104, "summary", "summary")
- attempt2 := insertAttempt(t, 2, StatusSkipped)
- insertCaller(t, attempt2.ID, StatusSkipped, false) // no children intentionally
+ attempt2 := insertAttempt(t, 2, StatusSkipped)
+ insertCaller(t, attempt2.ID, StatusSkipped, false) // no children intentionally
- // both subtests expect attempt 1's expansion, differing only in the "current" attempt id
- assertAttempt1Children := func(t *testing.T, out map[string]map[string]*ActionRunJob) {
- t.Helper()
- // outer map keyed by JobID: "work" has 3 matrix instances, "summary" 1
- assert.Len(t, out, 2)
- assert.Len(t, out["work"], 3, "matrix instances must each get their own inner-map entry")
- assert.Len(t, out["summary"], 1)
+ // both subtests expect attempt 1's expansion, differing only in the "current" attempt id
+ assertAttempt1Children := func(t *testing.T, out map[string]map[string]*ActionRunJob) {
+ t.Helper()
+ // outer map keyed by JobID: "work" has 3 matrix instances, "summary" 1
+ assert.Len(t, out, 2)
+ assert.Len(t, out["work"], 3, "matrix instances must each get their own inner-map entry")
+ assert.Len(t, out["summary"], 1)
- require.NotNil(t, out["work"]["work (alpha)"])
- require.NotNil(t, out["work"]["work (beta)"])
- require.NotNil(t, out["work"]["work (gamma)"])
- require.NotNil(t, out["summary"]["summary"])
+ require.NotNil(t, out["work"]["work (alpha)"])
+ require.NotNil(t, out["work"]["work (beta)"])
+ require.NotNil(t, out["work"]["work (gamma)"])
+ require.NotNil(t, out["summary"]["summary"])
- assert.Equal(t, int64(101), out["work"]["work (alpha)"].AttemptJobID)
- assert.Equal(t, int64(102), out["work"]["work (beta)"].AttemptJobID)
- assert.Equal(t, int64(103), out["work"]["work (gamma)"].AttemptJobID)
- assert.Equal(t, int64(104), out["summary"]["summary"].AttemptJobID)
- }
+ assert.Equal(t, int64(101), out["work"]["work (alpha)"].AttemptJobID)
+ assert.Equal(t, int64(102), out["work"]["work (beta)"].AttemptJobID)
+ assert.Equal(t, int64(103), out["work"]["work (gamma)"].AttemptJobID)
+ assert.Equal(t, int64(104), out["summary"]["summary"].AttemptJobID)
+ }
- t.Run("matrix instances and non-matrix sibling are indexed by (JobID, Name)", func(t *testing.T) {
- // "current" = attempt 2; prior = attempt 1, which is the immediately preceding attempt.
- out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt2.ID, callerAttemptJobID)
- require.NoError(t, err)
- assertAttempt1Children(t, out)
+ t.Run("matrix instances and non-matrix sibling are indexed by (JobID, Name)", func(t *testing.T) {
+ // "current" = attempt 2; prior = attempt 1, which is the immediately preceding attempt.
+ out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt2.ID, callerAttemptJobID)
+ require.NoError(t, err)
+ assertAttempt1Children(t, out)
+ })
+
+ t.Run("walkback past an attempt where the caller had no children", func(t *testing.T) {
+ attempt3 := insertAttempt(t, 3, StatusRunning)
+ // "current" = attempt 3; the immediately preceding attempt 2 has no children, so the lookup must walk further back to attempt 1.
+ out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt3.ID, callerAttemptJobID)
+ require.NoError(t, err)
+ assertAttempt1Children(t, out)
+ })
})
- t.Run("walkback past an attempt where the caller had no children", func(t *testing.T) {
- attempt3 := insertAttempt(t, 3, StatusRunning)
- // "current" = attempt 3; the immediately preceding attempt 2 has no children, so the lookup must walk further back to attempt 1.
- out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt3.ID, callerAttemptJobID)
- require.NoError(t, err)
- assertAttempt1Children(t, out)
- })
-}
+ t.Run("cancel reusable callers", func(t *testing.T) {
+ for _, tc := range []struct {
+ name, title, workflowID string
+ index int64
+ status Status
+ }{
+ {"NestedBlockedReusableCaller", "cancel-nested-caller", "caller.yaml", 9701, StatusBlocked},
+ {"CallerWaitsForCancellingChild", "caller-cancelling-child", "test.yaml", 9811, StatusRunning},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ ctx := t.Context()
-// A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled.
-func TestCancelJobs_NestedBlockedReusableCaller(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
- ctx := t.Context()
+ run := &ActionRun{
+ Title: tc.title,
+ RepoID: 4,
+ Index: tc.index,
+ OwnerID: 1,
+ WorkflowID: tc.workflowID,
+ TriggerUserID: 1,
+ Ref: "refs/heads/master",
+ CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
+ Event: "push",
+ TriggerEvent: "push",
+ EventPayload: "{}",
+ Status: tc.status,
+ }
+ require.NoError(t, db.Insert(ctx, run))
- run := &ActionRun{
- Title: "cancel-nested-caller",
- RepoID: 4,
- Index: 9701,
- OwnerID: 1,
- WorkflowID: "caller.yaml",
- TriggerUserID: 1,
- Ref: "refs/heads/master",
- CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
- Event: "push",
- TriggerEvent: "push",
- EventPayload: "{}",
- Status: StatusBlocked,
- }
- require.NoError(t, db.Insert(ctx, run))
+ attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: tc.status}
+ require.NoError(t, db.Insert(ctx, attempt))
+ run.LatestAttemptID = attempt.ID
+ require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
- attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusBlocked}
- require.NoError(t, db.Insert(ctx, attempt))
- run.LatestAttemptID = attempt.ID
- require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
+ newJob := func(job *ActionRunJob) *ActionRunJob {
+ job.RunID = run.ID
+ job.RunAttemptID = attempt.ID
+ job.RepoID = run.RepoID
+ job.OwnerID = run.OwnerID
+ job.CommitSHA = run.CommitSHA
+ job.JobID = job.Name
+ job.Attempt = 1
+ job.Status = tc.status
+ require.NoError(t, db.Insert(ctx, job))
+ return job
+ }
- newJob := func(name string, attemptJobID, parentID int64, callUses string) *ActionRunJob {
- job := &ActionRunJob{
- RunID: run.ID,
- RunAttemptID: attempt.ID,
- RepoID: run.RepoID,
- OwnerID: run.OwnerID,
- CommitSHA: run.CommitSHA,
- Name: name,
- JobID: name,
- Attempt: 1,
- Status: StatusBlocked,
- AttemptJobID: attemptJobID,
- IsReusableCaller: true,
- CallUses: callUses,
- ParentJobID: parentID,
+ // A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled.
+ if tc.status == StatusBlocked {
+ // outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands).
+ outer := newJob(&ActionRunJob{Name: "outer", AttemptJobID: 1, IsReusableCaller: true, CallUses: "./.gitea/workflows/lib.yml"})
+ inner := newJob(&ActionRunJob{Name: "inner", AttemptJobID: 2, ParentJobID: outer.ID, IsReusableCaller: true, CallUses: "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1"})
+
+ // Cancel all jobs of the attempt, ordered by id (parent before child).
+ jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID)
+ require.NoError(t, err)
+ _, err = CancelJobs(ctx, jobs, false)
+ require.NoError(t, err)
+
+ for _, j := range []*ActionRunJob{outer, inner} {
+ got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID})
+ assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID)
+ }
+ gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID})
+ assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled")
+ gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID})
+ assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked")
+ } else {
+ outer := newJob(&ActionRunJob{Name: "outer", IsReusableCaller: true, IsExpanded: true})
+ inner := newJob(&ActionRunJob{Name: "inner", ParentJobID: outer.ID, IsReusableCaller: true, IsExpanded: true})
+ child := newJob(&ActionRunJob{Name: "child", ParentJobID: inner.ID})
+
+ runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true}
+ require.NoError(t, db.Insert(ctx, runner))
+ task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA}
+ require.NoError(t, db.Insert(ctx, task))
+ child.TaskID = task.ID
+ _, err := UpdateRunJob(ctx, child, nil, "task_id")
+ require.NoError(t, err)
+
+ cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false)
+ require.NoError(t, err)
+ assert.Len(t, cancelled, 3)
+ for _, job := range []*ActionRunJob{outer, inner, child} {
+ assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
+ }
+
+ _, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED})
+ require.NoError(t, err)
+ for _, job := range []*ActionRunJob{outer, inner, child} {
+ assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
+ }
+ assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status)
+ }
+ })
}
- require.NoError(t, db.Insert(ctx, job))
- return job
- }
+ })
- // outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands).
- outer := newJob("outer", 1, 0, "./.gitea/workflows/lib.yml")
- inner := newJob("inner", 2, outer.ID, "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1")
+ t.Run("batch load and cancel run jobs", func(t *testing.T) {
+ ctx := t.Context()
- // Cancel all jobs of the attempt, ordered by id (parent before child).
- jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID)
- require.NoError(t, err)
- _, err = CancelJobs(ctx, jobs, false)
- require.NoError(t, err)
+ legacyRun := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9600, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning}
+ run := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9601, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning}
+ require.NoError(t, db.Insert(ctx, legacyRun, run))
+ require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: legacyRun.ID, RepoID: legacyRun.RepoID, OwnerID: legacyRun.OwnerID}))
+ oldAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1}
+ latestAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 2, TriggerUserID: 2}
+ require.NoError(t, db.Insert(ctx, oldAttempt, latestAttempt))
+ for _, attemptID := range []int64{oldAttempt.ID, latestAttempt.ID, latestAttempt.ID} {
+ require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: run.ID, RunAttemptID: attemptID, RepoID: run.RepoID, OwnerID: run.OwnerID}))
+ }
+ run.LatestAttemptID = latestAttempt.ID
+ require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
- for _, j := range []*ActionRunJob{outer, inner} {
- got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID})
- assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID)
- }
- gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID})
- assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled")
- gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID})
- assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked")
+ runs := RunList{legacyRun, run}
+ jobsByRun, err := GetLatestAttemptJobsByRuns(ctx, runs)
+ require.NoError(t, err)
+ for _, listedRun := range runs {
+ expected, err := GetLatestAttemptJobsByRun(ctx, listedRun)
+ require.NoError(t, err)
+ require.NotEmpty(t, expected)
+ assert.Equal(t, expected, jobsByRun[listedRun.ID])
+ }
+
+ attempts, err := runs.GetLatestAttempts(ctx)
+ require.NoError(t, err)
+ require.Len(t, attempts, 1)
+ assert.EqualValues(t, 2, attempts[latestAttempt.ID].TriggerUser.ID)
+
+ chunkedAttempts := []*ActionRunAttempt{oldAttempt}
+ for i := range db.DefaultMaxInSize {
+ chunkedAttempts = append(chunkedAttempts, &ActionRunAttempt{ID: -int64(i + 1)})
+ }
+ attemptJobs, err := getRunJobsByAttempts(ctx, append(chunkedAttempts, latestAttempt))
+ require.NoError(t, err)
+ assert.Len(t, attemptJobs, 3)
+
+ cancelled, err := CancelPreviousJobs(ctx, run.RepoID, run.Ref, "", "")
+ require.NoError(t, err)
+ assert.Len(t, cancelled, 4)
+ })
}
func TestSettleRunAfterCancel(t *testing.T) {
@@ -398,117 +476,3 @@ func TestForceCancelJobs(t *testing.T) {
assert.Equal(t, StatusCancelled, callerAfter.Status)
})
}
-
-func TestCancelJobs_CallerWaitsForCancellingChild(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
- ctx := t.Context()
-
- run := &ActionRun{
- Title: "caller-cancelling-child",
- RepoID: 4,
- Index: 9811,
- OwnerID: 1,
- WorkflowID: "test.yaml",
- TriggerUserID: 1,
- Ref: "refs/heads/master",
- CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
- Event: "push",
- TriggerEvent: "push",
- EventPayload: "{}",
- Status: StatusRunning,
- }
- require.NoError(t, db.Insert(ctx, run))
- attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusRunning}
- require.NoError(t, db.Insert(ctx, attempt))
- run.LatestAttemptID = attempt.ID
- require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
-
- newJob := func(name string, parentID int64, isCaller bool) *ActionRunJob {
- job := &ActionRunJob{
- RunID: run.ID,
- RunAttemptID: attempt.ID,
- RepoID: run.RepoID,
- OwnerID: run.OwnerID,
- CommitSHA: run.CommitSHA,
- Name: name,
- JobID: name,
- Attempt: 1,
- Status: StatusRunning,
- ParentJobID: parentID,
- IsReusableCaller: isCaller,
- IsExpanded: isCaller,
- }
- require.NoError(t, db.Insert(ctx, job))
- return job
- }
- outer := newJob("outer", 0, true)
- inner := newJob("inner", outer.ID, true)
- child := newJob("child", inner.ID, false)
-
- runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true}
- require.NoError(t, db.Insert(ctx, runner))
- task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA}
- require.NoError(t, db.Insert(ctx, task))
- child.TaskID = task.ID
- _, err := UpdateRunJob(ctx, child, nil, "task_id")
- require.NoError(t, err)
-
- cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false)
- require.NoError(t, err)
- assert.Len(t, cancelled, 3)
- for _, job := range []*ActionRunJob{outer, inner, child} {
- assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
- }
-
- _, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED})
- require.NoError(t, err)
- for _, job := range []*ActionRunJob{outer, inner, child} {
- assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
- }
- assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status)
-}
-
-func TestBatchLoadAndCancelRunJobs(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
- ctx := t.Context()
-
- legacyRun := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9600, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning}
- run := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9601, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning}
- require.NoError(t, db.Insert(ctx, legacyRun, run))
- require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: legacyRun.ID, RepoID: legacyRun.RepoID, OwnerID: legacyRun.OwnerID}))
- oldAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1}
- latestAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 2, TriggerUserID: 2}
- require.NoError(t, db.Insert(ctx, oldAttempt, latestAttempt))
- for _, attemptID := range []int64{oldAttempt.ID, latestAttempt.ID, latestAttempt.ID} {
- require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: run.ID, RunAttemptID: attemptID, RepoID: run.RepoID, OwnerID: run.OwnerID}))
- }
- run.LatestAttemptID = latestAttempt.ID
- require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
-
- runs := RunList{legacyRun, run}
- jobsByRun, err := GetLatestAttemptJobsByRuns(ctx, runs)
- require.NoError(t, err)
- for _, listedRun := range runs {
- expected, err := GetLatestAttemptJobsByRun(ctx, listedRun)
- require.NoError(t, err)
- require.NotEmpty(t, expected)
- assert.Equal(t, expected, jobsByRun[listedRun.ID])
- }
-
- attempts, err := runs.GetLatestAttempts(ctx)
- require.NoError(t, err)
- require.Len(t, attempts, 1)
- assert.EqualValues(t, 2, attempts[latestAttempt.ID].TriggerUser.ID)
-
- chunkedAttempts := []*ActionRunAttempt{oldAttempt}
- for i := range db.DefaultMaxInSize {
- chunkedAttempts = append(chunkedAttempts, &ActionRunAttempt{ID: -int64(i + 1)})
- }
- attemptJobs, err := getRunJobsByAttempts(ctx, append(chunkedAttempts, latestAttempt))
- require.NoError(t, err)
- assert.Len(t, attemptJobs, 3)
-
- cancelled, err := CancelPreviousJobs(ctx, run.RepoID, run.Ref, "", "")
- require.NoError(t, err)
- assert.Len(t, cancelled, 4)
-}
diff --git a/models/actions/runner_test.go b/models/actions/runner_test.go
index 3b5d0ea05ea..2a7ede74fce 100644
--- a/models/actions/runner_test.go
+++ b/models/actions/runner_test.go
@@ -12,72 +12,32 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestShouldPersistLastOnline(t *testing.T) {
- now := time.Now()
- tests := []struct {
- name string
- last timeutil.TimeStamp
- want bool
+func TestShouldPersistRunnerTimestamps(t *testing.T) {
+ for _, scenario := range []struct {
+ name string
+ persist func(timeutil.TimeStamp, time.Time) bool
+ interval time.Duration
+ freshAge time.Duration
}{
- {
- name: "fresh, skip write",
- last: timeutil.TimeStamp(now.Add(-5 * time.Second).Unix()),
- want: false,
- },
- {
- name: "exactly at interval, write",
- last: timeutil.TimeStamp(now.Add(-RunnerHeartbeatInterval).Unix()),
- want: true,
- },
- {
- name: "stale, write",
- last: timeutil.TimeStamp(now.Add(-2 * RunnerHeartbeatInterval).Unix()),
- want: true,
- },
- {
- name: "zero (never seen), write",
- last: 0,
- want: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equal(t, tt.want, ShouldPersistLastOnline(tt.last, now))
- })
- }
-}
-
-func TestShouldPersistLastActive(t *testing.T) {
- now := time.Now()
- tests := []struct {
- name string
- last timeutil.TimeStamp
- want bool
- }{
- {
- name: "fresh, skip write",
- last: timeutil.TimeStamp(now.Add(-1 * time.Second).Unix()),
- want: false,
- },
- {
- name: "exactly at interval, write",
- last: timeutil.TimeStamp(now.Add(-RunnerActiveInterval).Unix()),
- want: true,
- },
- {
- name: "stale, write",
- last: timeutil.TimeStamp(now.Add(-2 * RunnerActiveInterval).Unix()),
- want: true,
- },
- {
- name: "zero (never seen), write",
- last: 0,
- want: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equal(t, tt.want, ShouldPersistLastActive(tt.last, now))
+ {"online", ShouldPersistLastOnline, RunnerHeartbeatInterval, 5 * time.Second},
+ {"active", ShouldPersistLastActive, RunnerActiveInterval, time.Second},
+ } {
+ t.Run(scenario.name, func(t *testing.T) {
+ now := time.Now()
+ for _, tt := range []struct {
+ name string
+ last timeutil.TimeStamp
+ want bool
+ }{
+ {"fresh, skip write", timeutil.TimeStamp(now.Add(-scenario.freshAge).Unix()), false},
+ {"exactly at interval, write", timeutil.TimeStamp(now.Add(-scenario.interval).Unix()), true},
+ {"stale, write", timeutil.TimeStamp(now.Add(-2 * scenario.interval).Unix()), true},
+ {"zero (never seen), write", 0, true},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equal(t, tt.want, scenario.persist(tt.last, now))
+ })
+ }
})
}
}
diff --git a/models/actions/status_test.go b/models/actions/status_test.go
index 12e1b61e85f..8b2410d68a3 100644
--- a/models/actions/status_test.go
+++ b/models/actions/status_test.go
@@ -11,41 +11,28 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestStatusAsResult(t *testing.T) {
+func TestStatusResultConversions(t *testing.T) {
cases := []struct {
- status Status
- want runnerv1.Result
+ status Status
+ result runnerv1.Result
+ fromResult bool
}{
- {StatusUnknown, runnerv1.Result_RESULT_UNSPECIFIED},
- {StatusWaiting, runnerv1.Result_RESULT_UNSPECIFIED},
- {StatusRunning, runnerv1.Result_RESULT_UNSPECIFIED},
- {StatusBlocked, runnerv1.Result_RESULT_UNSPECIFIED},
- {StatusSuccess, runnerv1.Result_RESULT_SUCCESS},
- {StatusFailure, runnerv1.Result_RESULT_FAILURE},
- {StatusCancelled, runnerv1.Result_RESULT_CANCELLED},
- {StatusCancelling, runnerv1.Result_RESULT_CANCELLED},
- {StatusSkipped, runnerv1.Result_RESULT_SKIPPED},
+ {StatusUnknown, runnerv1.Result_RESULT_UNSPECIFIED, true},
+ {StatusWaiting, runnerv1.Result_RESULT_UNSPECIFIED, false},
+ {StatusRunning, runnerv1.Result_RESULT_UNSPECIFIED, false},
+ {StatusBlocked, runnerv1.Result_RESULT_UNSPECIFIED, false},
+ {StatusSuccess, runnerv1.Result_RESULT_SUCCESS, true},
+ {StatusFailure, runnerv1.Result_RESULT_FAILURE, true},
+ {StatusCancelled, runnerv1.Result_RESULT_CANCELLED, true},
+ {StatusCancelling, runnerv1.Result_RESULT_CANCELLED, false},
+ {StatusSkipped, runnerv1.Result_RESULT_SKIPPED, true},
}
for _, tt := range cases {
- assert.Equal(t, tt.want, tt.status.AsResult(), "status=%s", tt.status)
- }
-}
-
-func TestStatusFromResult(t *testing.T) {
- cases := []struct {
- result runnerv1.Result
- want Status
- }{
- {runnerv1.Result_RESULT_UNSPECIFIED, StatusUnknown},
- {runnerv1.Result_RESULT_SUCCESS, StatusSuccess},
- {runnerv1.Result_RESULT_FAILURE, StatusFailure},
- {runnerv1.Result_RESULT_CANCELLED, StatusCancelled},
- {runnerv1.Result_RESULT_SKIPPED, StatusSkipped},
- }
-
- for _, tt := range cases {
- assert.Equal(t, tt.want, StatusFromResult(tt.result), "result=%s", tt.result)
+ assert.Equal(t, tt.result, tt.status.AsResult(), "status=%s", tt.status)
+ if tt.fromResult {
+ assert.Equal(t, tt.status, StatusFromResult(tt.result), "result=%s", tt.result)
+ }
}
}
diff --git a/models/actions/task_test.go b/models/actions/task_test.go
index 12fa5468777..b8b27a7a58e 100644
--- a/models/actions/task_test.go
+++ b/models/actions/task_test.go
@@ -241,136 +241,106 @@ func TestStopTaskCancellingKeepsReportTime(t *testing.T) {
// its job to the waiting queue and deletes the task and its steps, so a failure
// while assembling the runner response cannot strand the job in running state.
func TestReleaseTaskForRunner(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
+ for _, tt := range []struct {
+ name string
+ runTitle string
+ runIndex int64
+ jobName string
+ batchSize int
+ }{
+ {"release", "release-task-test-run", 9902, "release-job", 0},
+ {"pagination", "pagination-test-run", 9903, "target-job", 2},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ require.NoError(t, unittest.PrepareTestDatabase())
+ if tt.batchSize != 0 {
+ defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize)
+ pickTaskBatchSize = tt.batchSize
+ }
- run := &ActionRun{
- Title: "release-task-test-run",
- RepoID: 1,
- OwnerID: 2,
- WorkflowID: "test.yaml",
- Index: 9902,
- TriggerUserID: 2,
- Ref: "refs/heads/main",
- CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
- Event: "push",
- TriggerEvent: "push",
- Status: StatusWaiting,
+ run := &ActionRun{
+ Title: tt.runTitle,
+ RepoID: 1,
+ OwnerID: 2,
+ WorkflowID: "test.yaml",
+ Index: tt.runIndex,
+ TriggerUserID: 2,
+ Ref: "refs/heads/main",
+ CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
+ Event: "push",
+ TriggerEvent: "push",
+ Status: StatusWaiting,
+ }
+ require.NoError(t, db.Insert(t.Context(), run))
+
+ if tt.batchSize != 0 {
+ // Five waiting jobs the runner cannot run, then one it can.
+ // With a page size of 2 the matching job only appears on the third page.
+ for i := range 5 {
+ mismatch := &ActionRunJob{
+ RunID: run.ID,
+ RepoID: run.RepoID,
+ OwnerID: run.OwnerID,
+ CommitSHA: run.CommitSHA,
+ Name: "mismatch-" + string(rune('a'+i)),
+ Attempt: 1,
+ JobID: "mismatch-" + string(rune('a'+i)),
+ Status: StatusWaiting,
+ RunsOn: []string{"windows-latest"},
+ }
+ require.NoError(t, db.Insert(t.Context(), mismatch))
+ }
+ }
+
+ job := &ActionRunJob{
+ RunID: run.ID,
+ RepoID: run.RepoID,
+ OwnerID: run.OwnerID,
+ CommitSHA: run.CommitSHA,
+ Name: tt.jobName,
+ Attempt: 1,
+ JobID: tt.jobName,
+ Status: StatusWaiting,
+ RunsOn: []string{"ubuntu-latest"},
+ WorkflowPayload: []byte("on: push\njobs:\n " + tt.jobName + ":\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
+ }
+ require.NoError(t, db.Insert(t.Context(), job))
+
+ runner := &ActionRunner{
+ UUID: tt.name + "-runner-uuid",
+ Name: tt.name + "-runner",
+ AgentLabels: []string{"ubuntu-latest"},
+ }
+ runner.GenerateAndFillToken()
+ require.NoError(t, db.Insert(t.Context(), runner))
+
+ task, ok, err := CreateTaskForRunner(t.Context(), runner)
+ require.NoError(t, err)
+ require.True(t, ok)
+ require.NotNil(t, task)
+
+ claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
+ if tt.batchSize != 0 {
+ assert.Equal(t, StatusRunning, claimed.Status)
+ assert.Equal(t, task.ID, claimed.TaskID)
+ } else {
+ require.Equal(t, StatusRunning, claimed.Status)
+ require.Equal(t, task.ID, claimed.TaskID)
+
+ require.NoError(t, ReleaseTaskForRunner(t.Context(), task))
+
+ // Job is back in the waiting queue with no task assigned.
+ released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
+ assert.Equal(t, StatusWaiting, released.Status)
+ assert.Zero(t, released.TaskID)
+ assert.Zero(t, released.Started)
+
+ // The task and its steps are gone.
+ unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
+ unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
+ }
+ })
}
- require.NoError(t, db.Insert(t.Context(), run))
-
- job := &ActionRunJob{
- RunID: run.ID,
- RepoID: run.RepoID,
- OwnerID: run.OwnerID,
- CommitSHA: run.CommitSHA,
- Name: "release-job",
- Attempt: 1,
- JobID: "release-job",
- Status: StatusWaiting,
- RunsOn: []string{"ubuntu-latest"},
- WorkflowPayload: []byte("on: push\njobs:\n release-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
- }
- require.NoError(t, db.Insert(t.Context(), job))
-
- runner := &ActionRunner{
- UUID: "release-runner-uuid",
- Name: "release-runner",
- AgentLabels: []string{"ubuntu-latest"},
- }
- runner.GenerateAndFillToken()
- require.NoError(t, db.Insert(t.Context(), runner))
-
- task, ok, err := CreateTaskForRunner(t.Context(), runner)
- require.NoError(t, err)
- require.True(t, ok)
- require.NotNil(t, task)
-
- claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
- require.Equal(t, StatusRunning, claimed.Status)
- require.Equal(t, task.ID, claimed.TaskID)
-
- require.NoError(t, ReleaseTaskForRunner(t.Context(), task))
-
- // Job is back in the waiting queue with no task assigned.
- released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
- assert.Equal(t, StatusWaiting, released.Status)
- assert.Zero(t, released.TaskID)
- assert.Zero(t, released.Started)
-
- // The task and its steps are gone.
- unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
- unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
-}
-
-// TestCreateTaskForRunnerPagination verifies that a job sitting beyond the first page is still claimed
-func TestCreateTaskForRunnerPagination(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
-
- defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize)
- pickTaskBatchSize = 2
-
- run := &ActionRun{
- Title: "pagination-test-run",
- RepoID: 1,
- OwnerID: 2,
- WorkflowID: "test.yaml",
- Index: 9903,
- TriggerUserID: 2,
- Ref: "refs/heads/main",
- CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
- Event: "push",
- TriggerEvent: "push",
- Status: StatusWaiting,
- }
- require.NoError(t, db.Insert(t.Context(), run))
-
- // Five waiting jobs the runner cannot run, then one it can.
- // With a page size of 2 the matching job only appears on the third page.
- for i := range 5 {
- mismatch := &ActionRunJob{
- RunID: run.ID,
- RepoID: run.RepoID,
- OwnerID: run.OwnerID,
- CommitSHA: run.CommitSHA,
- Name: "mismatch-" + string(rune('a'+i)),
- Attempt: 1,
- JobID: "mismatch-" + string(rune('a'+i)),
- Status: StatusWaiting,
- RunsOn: []string{"windows-latest"},
- }
- require.NoError(t, db.Insert(t.Context(), mismatch))
- }
-
- target := &ActionRunJob{
- RunID: run.ID,
- RepoID: run.RepoID,
- OwnerID: run.OwnerID,
- CommitSHA: run.CommitSHA,
- Name: "target-job",
- Attempt: 1,
- JobID: "target-job",
- Status: StatusWaiting,
- RunsOn: []string{"ubuntu-latest"},
- WorkflowPayload: []byte("on: push\njobs:\n target-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
- }
- require.NoError(t, db.Insert(t.Context(), target))
-
- runner := &ActionRunner{
- UUID: "pagination-runner-uuid",
- Name: "pagination-runner",
- AgentLabels: []string{"ubuntu-latest"},
- }
- runner.GenerateAndFillToken()
- require.NoError(t, db.Insert(t.Context(), runner))
-
- task, ok, err := CreateTaskForRunner(t.Context(), runner)
- require.NoError(t, err)
- require.True(t, ok)
- require.NotNil(t, task)
-
- claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: target.ID})
- assert.Equal(t, StatusRunning, claimed.Status)
- assert.Equal(t, task.ID, claimed.TaskID)
}
type failFirstStepWrite struct{ fired atomic.Bool }
diff --git a/models/actions/utils_test.go b/models/actions/utils_test.go
index 4ccffa4c0c0..f091628b452 100644
--- a/models/actions/utils_test.go
+++ b/models/actions/utils_test.go
@@ -15,24 +15,14 @@ import (
)
func TestLogIndexes_ToDB(t *testing.T) {
- tests := []struct {
- indexes LogIndexes
- }{
- {
- indexes: []int64{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64},
- },
- }
- for _, tt := range tests {
- t.Run("", func(t *testing.T) {
- got, err := tt.indexes.ToDB()
- require.NoError(t, err)
+ inputIndexes := LogIndexes{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64}
+ got, err := inputIndexes.ToDB()
+ require.NoError(t, err)
- indexes := LogIndexes{}
- require.NoError(t, indexes.FromDB(got))
+ indexes := LogIndexes{}
+ require.NoError(t, indexes.FromDB(got))
- assert.Equal(t, tt.indexes, indexes)
- })
- }
+ assert.Equal(t, inputIndexes, indexes)
}
func Test_calculateDuration(t *testing.T) {
diff --git a/models/activities/action_test.go b/models/activities/action_test.go
index 0a60cd9c781..256b696e891 100644
--- a/models/activities/action_test.go
+++ b/models/activities/action_test.go
@@ -104,19 +104,14 @@ func TestConsistencyUpdateAction(t *testing.T) {
//
// Get rid of incorrectly set created_unix
//
- count, err := activities_model.CountActionCreatedUnixString(t.Context())
- assert.NoError(t, err)
- assert.EqualValues(t, 1, count)
- count, err = activities_model.FixActionCreatedUnixString(t.Context())
- assert.NoError(t, err)
- assert.EqualValues(t, 1, count)
-
- count, err = activities_model.CountActionCreatedUnixString(t.Context())
- assert.NoError(t, err)
- assert.EqualValues(t, 0, count)
- count, err = activities_model.FixActionCreatedUnixString(t.Context())
- assert.NoError(t, err)
- assert.EqualValues(t, 0, count)
+ for _, wantCount := range []int{1, 0} {
+ count, err := activities_model.CountActionCreatedUnixString(t.Context())
+ assert.NoError(t, err)
+ assert.EqualValues(t, wantCount, count)
+ count, err = activities_model.FixActionCreatedUnixString(t.Context())
+ assert.NoError(t, err)
+ assert.EqualValues(t, wantCount, count)
+ }
//
// XORM must be happy now
diff --git a/models/activities/notification_test.go b/models/activities/notification_test.go
index beb404d0351..1273f9c5136 100644
--- a/models/activities/notification_test.go
+++ b/models/activities/notification_test.go
@@ -196,23 +196,17 @@ func TestNotification_GetIssue(t *testing.T) {
func TestGetNotificationCount(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
- cnt, err := db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
- UserID: user.ID,
- Status: []activities_model.NotificationStatus{
- activities_model.NotificationStatusRead,
- },
- })
- assert.NoError(t, err)
- assert.EqualValues(t, 0, cnt)
-
- cnt, err = db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
- UserID: user.ID,
- Status: []activities_model.NotificationStatus{
- activities_model.NotificationStatusUnread,
- },
- })
- assert.NoError(t, err)
- assert.EqualValues(t, 1, cnt)
+ for count, status := range []activities_model.NotificationStatus{
+ activities_model.NotificationStatusRead,
+ activities_model.NotificationStatusUnread,
+ } {
+ cnt, err := db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
+ UserID: user.ID,
+ Status: []activities_model.NotificationStatus{status},
+ })
+ assert.NoError(t, err)
+ assert.EqualValues(t, count, cnt)
+ }
}
func TestSetNotificationStatus(t *testing.T) {
diff --git a/models/asymkey/gpg_key_test.go b/models/asymkey/gpg_key_test.go
index 5cce164a78f..5b722d2c6d7 100644
--- a/models/asymkey/gpg_key_test.go
+++ b/models/asymkey/gpg_key_test.go
@@ -18,43 +18,6 @@ import (
"github.com/stretchr/testify/require"
)
-func TestCheckArmoredGPGKeyString(t *testing.T) {
- testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
-
-mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv
-z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m
-/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1
-vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN
-0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac
-mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE
-IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF
-Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY
-KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa
-MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ
-ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+
-sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo
-T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i
-iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE
-QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT
-pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU
-JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN
-/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx
-ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02
-cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF
-CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH
-6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk
-lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo
-RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP
-Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR
-MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
-=i9b7
------END PGP PUBLIC KEY BLOCK-----`
-
- key, err := CheckArmoredGPGKeyString(testGPGArmor)
- assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", key)
- // TODO verify value of key
-}
-
func TestCheckArmoredbrainpoolP256r1GPGKeyString(t *testing.T) {
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2
@@ -108,6 +71,10 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
=i9b7
-----END PGP PUBLIC KEY BLOCK-----`
keys, err := CheckArmoredGPGKeyString(testGPGArmor)
+ assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", keys)
+ // TODO verify value of key
+
+ keys, err = CheckArmoredGPGKeyString(testGPGArmor)
require.NotEmpty(t, keys)
ekey := keys[0]
diff --git a/models/asymkey/ssh_key_test.go b/models/asymkey/ssh_key_test.go
index a90bcad246a..88c5256eff1 100644
--- a/models/asymkey/ssh_key_test.go
+++ b/models/asymkey/ssh_key_test.go
@@ -23,18 +23,18 @@ import (
func Test_SSHParsePublicKey(t *testing.T) {
testCases := []struct {
- name string
- skipSSHKeygen bool
- keyType string
- length int
- content string
+ name string
+ keyType string
+ length int
+ fp string
+ content string
}{
- {"rsa-1024", false, "rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
- {"rsa-2048", false, "rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
- {"ecdsa-256", false, "ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
- {"ecdsa-384", false, "ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
- {"ecdsa-sk", true, "ecdsa-sk", 256, "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
- {"ed25519-sk", true, "ed25519-sk", 256, "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
+ {"rsa-1024", "rsa", 1024, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
+ {"rsa-2048", "rsa", 2048, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
+ {"ecdsa-256", "ecdsa", 256, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
+ {"ecdsa-384", "ecdsa", 384, "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
+ {"ecdsa-sk", "ecdsa-sk", 256, "SHA256:4wcIu4z+53gHc+db85OPfy8IydyNzPLCr6kHIs625LQ", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
+ {"ed25519-sk", "ed25519-sk", 256, "SHA256:RB4ku1OeWKN7fLMrjxz38DK0mp1BnOPBx4BItjTvJ0g", "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
}
for _, tc := range testCases {
@@ -44,6 +44,10 @@ func Test_SSHParsePublicKey(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, tc.keyType, keyTypeN)
assert.Equal(t, tc.length, lengthN)
+
+ fpN, err := calcFingerprintNative(tc.content)
+ assert.NoError(t, err)
+ assert.Equal(t, tc.fp, fpN)
})
})
}
@@ -143,32 +147,6 @@ AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf
}
}
-func Test_calcFingerprint(t *testing.T) {
- testCases := []struct {
- name string
- skipSSHKeygen bool
- fp string
- content string
- }{
- {"rsa-1024", false, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
- {"rsa-2048", false, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
- {"ecdsa-256", false, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
- {"ecdsa-384", false, "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
- {"ecdsa-sk", true, "SHA256:4wcIu4z+53gHc+db85OPfy8IydyNzPLCr6kHIs625LQ", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
- {"ed25519-sk", true, "SHA256:RB4ku1OeWKN7fLMrjxz38DK0mp1BnOPBx4BItjTvJ0g", "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Run("Native", func(t *testing.T) {
- fpN, err := calcFingerprintNative(tc.content)
- assert.NoError(t, err)
- assert.Equal(t, tc.fp, fpN)
- })
- })
- }
-}
-
var (
// Generated with "ssh-keygen -C test@rekor.dev -f id_rsa"
sshPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
diff --git a/models/audit/audit_event_test.go b/models/audit/audit_event_test.go
index 46bc470363e..b3e183faa20 100644
--- a/models/audit/audit_event_test.go
+++ b/models/audit/audit_event_test.go
@@ -17,35 +17,29 @@ import (
func TestFindEventsScopeFilters(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
- events := []*Event{
+ for _, event := range []*Event{
{Action: UserCreate, ScopeType: ScopeUser, ScopeID: 5, Origin: OriginUI, TimestampUnix: timeutil.TimeStamp(1)},
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 5, Origin: OriginAPI, TimestampUnix: timeutil.TimeStamp(1)},
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 6, Origin: OriginCLI, TimestampUnix: timeutil.TimeStamp(1)},
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 7, Origin: OriginSystem, TimestampUnix: timeutil.TimeStamp(1)},
- }
- for _, event := range events {
+ } {
require.NoError(t, InsertEvent(t.Context(), event))
}
- byType, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeType: ScopeRepository})
- require.NoError(t, err)
- assert.Len(t, byType, 3)
-
- byID, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeID: 5})
- require.NoError(t, err)
- assert.Len(t, byID, 2)
-
- byScope, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeType: ScopeRepository, ScopeID: 5})
- require.NoError(t, err)
- assert.Len(t, byScope, 1)
-
- byOrigin, _, err := FindEvents(t.Context(), &EventSearchOptions{Origin: OriginAPI})
- require.NoError(t, err)
- assert.Len(t, byOrigin, 1)
-
- bySystemOrigin, _, err := FindEvents(t.Context(), &EventSearchOptions{Origin: OriginSystem})
- require.NoError(t, err)
- assert.Len(t, bySystemOrigin, 1)
+ for _, test := range []struct {
+ options EventSearchOptions
+ count int
+ }{
+ {EventSearchOptions{ScopeType: ScopeRepository}, 3},
+ {EventSearchOptions{ScopeID: 5}, 2},
+ {EventSearchOptions{ScopeType: ScopeRepository, ScopeID: 5}, 1},
+ {EventSearchOptions{Origin: OriginAPI}, 1},
+ {EventSearchOptions{Origin: OriginSystem}, 1},
+ } {
+ events, _, err := FindEvents(t.Context(), &test.options)
+ require.NoError(t, err)
+ assert.Len(t, events, test.count)
+ }
}
func TestFindEventsActionPrefixFilter(t *testing.T) {
diff --git a/models/auth/access_token_test.go b/models/auth/access_token_test.go
index f2645aab118..053c4bb8cb2 100644
--- a/models/auth/access_token_test.go
+++ b/models/auth/access_token_test.go
@@ -14,97 +14,148 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestNewAccessToken(t *testing.T) {
+func TestAccessTokenLifecycle(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- token := &auth_model.AccessToken{
- UID: 3,
- Name: "Token C",
- }
- assert.NoError(t, auth_model.NewAccessToken(t.Context(), token))
- unittest.AssertExistsAndLoadBean(t, token)
- invalidToken := &auth_model.AccessToken{
- ID: token.ID, // duplicate
- UID: 2,
- Name: "Token F",
- }
- assert.Error(t, auth_model.NewAccessToken(t.Context(), invalidToken))
-}
+ t.Run("GetAccessTokenBySHA", func(t *testing.T) {
+ token, err := auth_model.GetAccessTokenBySHA(t.Context(), "d2c6c1ba3890b309189a8e618c72a162e4efbf36")
+ assert.NoError(t, err)
+ assert.Equal(t, int64(1), token.UID)
+ assert.Equal(t, "Token A", token.Name)
+ assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash)
+ assert.Equal(t, "e4efbf36", token.TokenLastEight)
-func TestAccessTokenByNameExists(t *testing.T) {
- name := "Token Gitea"
+ _, err = auth_model.GetAccessTokenBySHA(t.Context(), "notahash")
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrNotExist)
- assert.NoError(t, unittest.PrepareTestDatabase())
- token := &auth_model.AccessToken{
- UID: 3,
- Name: name,
- }
+ _, err = auth_model.GetAccessTokenBySHA(t.Context(), "")
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ })
- // Check to make sure it doesn't exists already
- exist, err := auth_model.AccessTokenByNameExists(t.Context(), token)
- assert.NoError(t, err)
- assert.False(t, exist)
+ t.Run("ListAccessTokens", func(t *testing.T) {
+ tokens, err := db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 1})
+ assert.NoError(t, err)
+ if assert.Len(t, tokens, 2) {
+ assert.Equal(t, int64(1), tokens[0].UID)
+ assert.Equal(t, int64(1), tokens[1].UID)
+ assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token A")
+ assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B")
+ }
- // Save it to the database
- assert.NoError(t, auth_model.NewAccessToken(t.Context(), token))
- unittest.AssertExistsAndLoadBean(t, token)
+ tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 2})
+ assert.NoError(t, err)
+ if assert.Len(t, tokens, 1) {
+ assert.Equal(t, int64(2), tokens[0].UID)
+ assert.Equal(t, "Token A", tokens[0].Name)
+ }
- // This token must be found by name in the DB now
- exist, err = auth_model.AccessTokenByNameExists(t.Context(), token)
- assert.NoError(t, err)
- assert.True(t, exist)
+ tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 100})
+ assert.NoError(t, err)
+ assert.Empty(t, tokens)
+ })
- user4Token := &auth_model.AccessToken{
- UID: 4,
- Name: name,
- }
+ t.Run("NewAccessToken", func(t *testing.T) {
+ token := &auth_model.AccessToken{
+ UID: 3,
+ Name: "Token C",
+ }
+ assert.NoError(t, auth_model.NewAccessToken(t.Context(), token))
+ unittest.AssertExistsAndLoadBean(t, token)
- // Name matches but different user ID, this shouldn't exists in the
- // database
- exist, err = auth_model.AccessTokenByNameExists(t.Context(), user4Token)
- assert.NoError(t, err)
- assert.False(t, exist)
-}
+ invalidToken := &auth_model.AccessToken{
+ ID: token.ID, // duplicate
+ UID: 2,
+ Name: "Token F",
+ }
+ assert.Error(t, auth_model.NewAccessToken(t.Context(), invalidToken))
+ })
-func TestGetAccessTokenBySHA(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- token, err := auth_model.GetAccessTokenBySHA(t.Context(), "d2c6c1ba3890b309189a8e618c72a162e4efbf36")
- assert.NoError(t, err)
- assert.Equal(t, int64(1), token.UID)
- assert.Equal(t, "Token A", token.Name)
- assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash)
- assert.Equal(t, "e4efbf36", token.TokenLastEight)
+ t.Run("AccessTokenByNameExists", func(t *testing.T) {
+ name := "Token Gitea"
- _, err = auth_model.GetAccessTokenBySHA(t.Context(), "notahash")
- assert.Error(t, err)
- assert.ErrorIs(t, err, util.ErrNotExist)
+ token := &auth_model.AccessToken{
+ UID: 3,
+ Name: name,
+ }
- _, err = auth_model.GetAccessTokenBySHA(t.Context(), "")
- assert.Error(t, err)
- assert.ErrorIs(t, err, util.ErrNotExist)
-}
+ // Check to make sure it doesn't exists already
+ exist, err := auth_model.AccessTokenByNameExists(t.Context(), token)
+ assert.NoError(t, err)
+ assert.False(t, exist)
-func TestListAccessTokens(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- tokens, err := db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 1})
- assert.NoError(t, err)
- if assert.Len(t, tokens, 2) {
- assert.Equal(t, int64(1), tokens[0].UID)
- assert.Equal(t, int64(1), tokens[1].UID)
- assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token A")
- assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B")
- }
+ // Save it to the database
+ assert.NoError(t, auth_model.NewAccessToken(t.Context(), token))
+ unittest.AssertExistsAndLoadBean(t, token)
- tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 2})
- assert.NoError(t, err)
- if assert.Len(t, tokens, 1) {
- assert.Equal(t, int64(2), tokens[0].UID)
- assert.Equal(t, "Token A", tokens[0].Name)
- }
+ // This token must be found by name in the DB now
+ exist, err = auth_model.AccessTokenByNameExists(t.Context(), token)
+ assert.NoError(t, err)
+ assert.True(t, exist)
- tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 100})
- assert.NoError(t, err)
- assert.Empty(t, tokens)
+ user4Token := &auth_model.AccessToken{
+ UID: 4,
+ Name: name,
+ }
+
+ // Name matches but different user ID, this shouldn't exists in the
+ // database
+ exist, err = auth_model.AccessTokenByNameExists(t.Context(), user4Token)
+ assert.NoError(t, err)
+ assert.False(t, exist)
+ })
+
+ t.Run("RegenerateAccessToken", func(t *testing.T) {
+ const oldToken = "d2c6c1ba3890b309189a8e618c72a162e4efbf36"
+
+ // prime the successful-lookup cache with the old token value, as a real request would
+ before, err := auth_model.GetAccessTokenBySHA(t.Context(), oldToken)
+ assert.NoError(t, err)
+ assert.Equal(t, "Token A", before.Name)
+
+ regenerated, err := auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID)
+ assert.NoError(t, err)
+ assert.Equal(t, before.ID, regenerated.ID)
+ assert.Equal(t, before.Name, regenerated.Name)
+ assert.Equal(t, before.Scope, regenerated.Scope)
+ assert.NotEqual(t, before.TokenHash, regenerated.TokenHash)
+ assert.NotEmpty(t, regenerated.Token)
+
+ // the old token value must stop authenticating, even though it was cached as successful above
+ _, err = auth_model.GetAccessTokenBySHA(t.Context(), oldToken)
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+
+ // the new token value must authenticate
+ found, err := auth_model.GetAccessTokenBySHA(t.Context(), regenerated.Token)
+ assert.NoError(t, err)
+ assert.Equal(t, before.ID, found.ID)
+ assert.Equal(t, before.UpdatedUnix, found.UpdatedUnix)
+
+ // wrong owner
+ _, err = auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID+1)
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+
+ // nonexistent token
+ _, err = auth_model.RegenerateAccessToken(t.Context(), 100, 100)
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ })
+
+ t.Run("DeleteAccessTokenByID", func(t *testing.T) {
+ token, err := auth_model.GetAccessTokenBySHA(t.Context(), "4c6f36e6cf498e2a448662f915d932c09c5a146c")
+ assert.NoError(t, err)
+ assert.Equal(t, int64(1), token.UID)
+
+ assert.NoError(t, auth_model.DeleteAccessTokenByID(t.Context(), token.ID, 1))
+ unittest.AssertNotExistsBean(t, token)
+
+ err = auth_model.DeleteAccessTokenByID(t.Context(), 100, 100)
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ })
}
func TestUpdateAccessToken(t *testing.T) {
@@ -116,58 +167,3 @@ func TestUpdateAccessToken(t *testing.T) {
assert.NoError(t, auth_model.UpdateAccessToken(t.Context(), token))
unittest.AssertExistsAndLoadBean(t, token)
}
-
-func TestRegenerateAccessToken(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- const oldToken = "d2c6c1ba3890b309189a8e618c72a162e4efbf36"
-
- // prime the successful-lookup cache with the old token value, as a real request would
- before, err := auth_model.GetAccessTokenBySHA(t.Context(), oldToken)
- assert.NoError(t, err)
- assert.Equal(t, "Token A", before.Name)
-
- regenerated, err := auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID)
- assert.NoError(t, err)
- assert.Equal(t, before.ID, regenerated.ID)
- assert.Equal(t, before.Name, regenerated.Name)
- assert.Equal(t, before.Scope, regenerated.Scope)
- assert.NotEqual(t, before.TokenHash, regenerated.TokenHash)
- assert.NotEmpty(t, regenerated.Token)
-
- // the old token value must stop authenticating, even though it was cached as successful above
- _, err = auth_model.GetAccessTokenBySHA(t.Context(), oldToken)
- assert.Error(t, err)
- assert.ErrorIs(t, err, util.ErrNotExist)
-
- // the new token value must authenticate
- found, err := auth_model.GetAccessTokenBySHA(t.Context(), regenerated.Token)
- assert.NoError(t, err)
- assert.Equal(t, before.ID, found.ID)
- assert.Equal(t, before.UpdatedUnix, found.UpdatedUnix)
-
- // wrong owner
- _, err = auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID+1)
- assert.Error(t, err)
- assert.ErrorIs(t, err, util.ErrNotExist)
-
- // nonexistent token
- _, err = auth_model.RegenerateAccessToken(t.Context(), 100, 100)
- assert.Error(t, err)
- assert.ErrorIs(t, err, util.ErrNotExist)
-}
-
-func TestDeleteAccessTokenByID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- token, err := auth_model.GetAccessTokenBySHA(t.Context(), "4c6f36e6cf498e2a448662f915d932c09c5a146c")
- assert.NoError(t, err)
- assert.Equal(t, int64(1), token.UID)
-
- assert.NoError(t, auth_model.DeleteAccessTokenByID(t.Context(), token.ID, 1))
- unittest.AssertNotExistsBean(t, token)
-
- err = auth_model.DeleteAccessTokenByID(t.Context(), 100, 100)
- assert.Error(t, err)
- assert.ErrorIs(t, err, util.ErrNotExist)
-}
diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go
index ea760eb2636..dee37d14067 100644
--- a/models/auth/oauth2_test.go
+++ b/models/auth/oauth2_test.go
@@ -57,13 +57,105 @@ func TestOAuth2AuthorizationCode(t *testing.T) {
})
}
-func TestOAuth2Application_GenerateClientSecret(t *testing.T) {
+func TestOAuth2ApplicationAndGrant(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
- secret, err := app.GenerateClientSecret(t.Context())
- assert.NoError(t, err)
- assert.NotEmpty(t, secret)
- unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret})
+
+ t.Run("GetOAuth2ApplicationByClientID", func(t *testing.T) {
+ app, err := auth_model.GetOAuth2ApplicationByClientID(t.Context(), "da7da3ba-9a13-4167-856f-3899de0b0138")
+ assert.NoError(t, err)
+ assert.Equal(t, "da7da3ba-9a13-4167-856f-3899de0b0138", app.ClientID)
+
+ app, err = auth_model.GetOAuth2ApplicationByClientID(t.Context(), "invalid client id")
+ assert.Error(t, err)
+ assert.Nil(t, app)
+ })
+
+ t.Run("OAuth2Application_GetGrantByUserID", func(t *testing.T) {
+ app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
+ grant, err := app.GetGrantByUserID(t.Context(), 1)
+ assert.NoError(t, err)
+ assert.Equal(t, int64(1), grant.UserID)
+
+ grant, err = app.GetGrantByUserID(t.Context(), 34923458)
+ assert.NoError(t, err)
+ assert.Nil(t, grant)
+ })
+
+ t.Run("GetOAuth2GrantByID", func(t *testing.T) {
+ grant, err := auth_model.GetOAuth2GrantByID(t.Context(), 1)
+ assert.NoError(t, err)
+ assert.Equal(t, int64(1), grant.ID)
+
+ grant, err = auth_model.GetOAuth2GrantByID(t.Context(), 34923458)
+ assert.NoError(t, err)
+ assert.Nil(t, grant)
+ })
+
+ t.Run("OAuth2Grant_ScopeContains", func(t *testing.T) {
+ grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Scope: "openid profile"})
+ assert.True(t, grant.ScopeContains("openid"))
+ assert.True(t, grant.ScopeContains("profile"))
+ assert.False(t, grant.ScopeContains("profil"))
+ assert.False(t, grant.ScopeContains("profile2"))
+ })
+
+ t.Run("GetOAuth2GrantsByUserID", func(t *testing.T) {
+ result, err := auth_model.GetOAuth2GrantsByUserID(t.Context(), 1)
+ assert.NoError(t, err)
+ assert.Len(t, result, 1)
+ assert.Equal(t, int64(1), result[0].ID)
+ assert.Equal(t, result[0].ApplicationID, result[0].Application.ID)
+
+ result, err = auth_model.GetOAuth2GrantsByUserID(t.Context(), 34134)
+ assert.NoError(t, err)
+ assert.Empty(t, result)
+ })
+
+ t.Run("OAuth2Grant_GenerateNewAuthorizationCode", func(t *testing.T) {
+ grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1})
+ code, err := grant.GenerateNewAuthorizationCode(t.Context(), "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256")
+ assert.NoError(t, err)
+ assert.NotNil(t, code)
+ assert.Greater(t, len(code.Code), 32) // secret length > 32
+ })
+
+ t.Run("OAuth2Application_CreateGrant", func(t *testing.T) {
+ app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
+ grant, err := app.CreateGrant(t.Context(), 2, "")
+ assert.NoError(t, err)
+ assert.NotNil(t, grant)
+ assert.Equal(t, int64(2), grant.UserID)
+ assert.Equal(t, int64(1), grant.ApplicationID)
+ assert.Empty(t, grant.Scope)
+ })
+
+ t.Run("OAuth2Grant_IncreaseCounter", func(t *testing.T) {
+ grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
+ stale := *grant
+ assert.NoError(t, grant.IncreaseCounter(t.Context()))
+ assert.Equal(t, int64(2), grant.Counter)
+ unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
+ err := stale.IncreaseCounter(t.Context())
+ assert.ErrorIs(t, err, auth_model.ErrOAuth2GrantStaleCounter)
+ })
+
+ t.Run("OAuth2Application_GenerateClientSecret", func(t *testing.T) {
+ app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
+ secret, err := app.GenerateClientSecret(t.Context())
+ assert.NoError(t, err)
+ assert.NotEmpty(t, secret)
+ unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret})
+ assert.True(t, app.ValidateClientSecret([]byte(secret)))
+ assert.False(t, app.ValidateClientSecret([]byte("fewijfowejgfiowjeoifew")))
+ })
+
+ t.Run("CreateOAuth2Application", func(t *testing.T) {
+ app, err := auth_model.CreateOAuth2Application(t.Context(), auth_model.CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1})
+ assert.NoError(t, err)
+ assert.Equal(t, "newapp", app.Name)
+ assert.Len(t, app.ClientID, 36)
+ unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{Name: "newapp"})
+ })
}
func BenchmarkOAuth2Application_GenerateClientSecret(b *testing.B) {
@@ -106,15 +198,12 @@ func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) {
}
func TestOAuth2Application_ContainsRedirect_Slash(t *testing.T) {
- app := &auth_model.OAuth2Application{RedirectURIs: []string{"http://127.0.0.1"}}
- assert.True(t, app.ContainsRedirectURI("http://127.0.0.1"))
- assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
- assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
-
- app = &auth_model.OAuth2Application{RedirectURIs: []string{"http://127.0.0.1/"}}
- assert.True(t, app.ContainsRedirectURI("http://127.0.0.1"))
- assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
- assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
+ for _, redirectURI := range []string{"http://127.0.0.1", "http://127.0.0.1/"} {
+ app := &auth_model.OAuth2Application{RedirectURIs: []string{redirectURI}}
+ assert.True(t, app.ContainsRedirectURI("http://127.0.0.1"))
+ assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
+ assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
+ }
}
func TestOAuth2Application_ContainsRedirectURI_ASCIIOnlyNormalization(t *testing.T) {
@@ -158,128 +247,16 @@ func TestOAuth2Application_ContainsRedirectURI_ASCIIOnlyNormalization(t *testing
}
}
-func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
- secret, err := app.GenerateClientSecret(t.Context())
- assert.NoError(t, err)
- assert.True(t, app.ValidateClientSecret([]byte(secret)))
- assert.False(t, app.ValidateClientSecret([]byte("fewijfowejgfiowjeoifew")))
-}
-
-func TestGetOAuth2ApplicationByClientID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- app, err := auth_model.GetOAuth2ApplicationByClientID(t.Context(), "da7da3ba-9a13-4167-856f-3899de0b0138")
- assert.NoError(t, err)
- assert.Equal(t, "da7da3ba-9a13-4167-856f-3899de0b0138", app.ClientID)
-
- app, err = auth_model.GetOAuth2ApplicationByClientID(t.Context(), "invalid client id")
- assert.Error(t, err)
- assert.Nil(t, app)
-}
-
-func TestCreateOAuth2Application(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- app, err := auth_model.CreateOAuth2Application(t.Context(), auth_model.CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1})
- assert.NoError(t, err)
- assert.Equal(t, "newapp", app.Name)
- assert.Len(t, app.ClientID, 36)
- unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{Name: "newapp"})
-}
-
func TestOAuth2Application_TableName(t *testing.T) {
assert.Equal(t, "oauth2_application", new(auth_model.OAuth2Application).TableName())
}
-func TestOAuth2Application_GetGrantByUserID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
- grant, err := app.GetGrantByUserID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Equal(t, int64(1), grant.UserID)
-
- grant, err = app.GetGrantByUserID(t.Context(), 34923458)
- assert.NoError(t, err)
- assert.Nil(t, grant)
-}
-
-func TestOAuth2Application_CreateGrant(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
- grant, err := app.CreateGrant(t.Context(), 2, "")
- assert.NoError(t, err)
- assert.NotNil(t, grant)
- assert.Equal(t, int64(2), grant.UserID)
- assert.Equal(t, int64(1), grant.ApplicationID)
- assert.Empty(t, grant.Scope)
-}
-
//////////////////// Grant
-func TestGetOAuth2GrantByID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- grant, err := auth_model.GetOAuth2GrantByID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Equal(t, int64(1), grant.ID)
-
- grant, err = auth_model.GetOAuth2GrantByID(t.Context(), 34923458)
- assert.NoError(t, err)
- assert.Nil(t, grant)
-}
-
-func TestOAuth2Grant_IncreaseCounter(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
- assert.NoError(t, grant.IncreaseCounter(t.Context()))
- assert.Equal(t, int64(2), grant.Counter)
- unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
-}
-
-func TestOAuth2Grant_IncreaseCounterRejectsStaleCounter(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
- stale := *grant
-
- assert.NoError(t, grant.IncreaseCounter(t.Context()))
- err := stale.IncreaseCounter(t.Context())
- assert.ErrorIs(t, err, auth_model.ErrOAuth2GrantStaleCounter)
-}
-
-func TestOAuth2Grant_ScopeContains(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Scope: "openid profile"})
- assert.True(t, grant.ScopeContains("openid"))
- assert.True(t, grant.ScopeContains("profile"))
- assert.False(t, grant.ScopeContains("profil"))
- assert.False(t, grant.ScopeContains("profile2"))
-}
-
-func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1})
- code, err := grant.GenerateNewAuthorizationCode(t.Context(), "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256")
- assert.NoError(t, err)
- assert.NotNil(t, code)
- assert.Greater(t, len(code.Code), 32) // secret length > 32
-}
-
func TestOAuth2Grant_TableName(t *testing.T) {
assert.Equal(t, "oauth2_grant", new(auth_model.OAuth2Grant).TableName())
}
-func TestGetOAuth2GrantsByUserID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- result, err := auth_model.GetOAuth2GrantsByUserID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Len(t, result, 1)
- assert.Equal(t, int64(1), result[0].ID)
- assert.Equal(t, result[0].ApplicationID, result[0].Application.ID)
-
- result, err = auth_model.GetOAuth2GrantsByUserID(t.Context(), 34134)
- assert.NoError(t, err)
- assert.Empty(t, result)
-}
-
func TestRevokeOAuth2Grant(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
assert.NoError(t, auth_model.RevokeOAuth2Grant(t.Context(), 1, 1))
diff --git a/models/auth/twofactor_test.go b/models/auth/twofactor_test.go
index 835e7c9c418..a3ed2187d0f 100644
--- a/models/auth/twofactor_test.go
+++ b/models/auth/twofactor_test.go
@@ -16,67 +16,68 @@ import (
"github.com/stretchr/testify/require"
)
-func TestTwoFactorValidateAndConsumeTOTP(t *testing.T) {
+func TestTwoFactorLifecycle(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
- key, err := totp.Generate(totp.GenerateOpts{SecretSize: 40, Issuer: "gitea-test", AccountName: "consume"})
- require.NoError(t, err)
+ t.Run("TwoFactorValidateAndConsumeTOTP", func(t *testing.T) {
+ key, err := totp.Generate(totp.GenerateOpts{SecretSize: 40, Issuer: "gitea-test", AccountName: "consume"})
+ require.NoError(t, err)
- tfa := &auth_model.TwoFactor{UID: 1}
- require.NoError(t, tfa.SetSecret(key.Secret()))
- require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa))
+ tfa := &auth_model.TwoFactor{UID: 1}
+ require.NoError(t, tfa.SetSecret(key.Secret()))
+ require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa))
- passcode, err := totp.GenerateCode(key.Secret(), time.Now())
- require.NoError(t, err)
+ passcode, err := totp.GenerateCode(key.Secret(), time.Now())
+ require.NoError(t, err)
- // first use of a valid passcode succeeds
- ok, err := tfa.ValidateAndConsumeTOTP(t.Context(), passcode)
- require.NoError(t, err)
- assert.True(t, ok)
+ // first use of a valid passcode succeeds
+ ok, err := tfa.ValidateAndConsumeTOTP(t.Context(), passcode)
+ require.NoError(t, err)
+ assert.True(t, ok)
- // replaying the same passcode is refused, even when still inside the TOTP validity window
- reloaded, err := auth_model.GetTwoFactorByUID(t.Context(), tfa.UID)
- require.NoError(t, err)
- ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), passcode)
- require.NoError(t, err)
- assert.False(t, ok)
+ // replaying the same passcode is refused, even when still inside the TOTP validity window
+ reloaded, err := auth_model.GetTwoFactorByUID(t.Context(), tfa.UID)
+ require.NoError(t, err)
+ ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), passcode)
+ require.NoError(t, err)
+ assert.False(t, ok)
- // an invalid passcode is rejected without consuming anything
- ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), "000000")
- require.NoError(t, err)
- assert.False(t, ok)
-}
-
-func TestDisableTwoFactor(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
- ctx := t.Context()
-
- const uid = 1000 // a uid with no user/2FA fixtures
-
- // Enroll TOTP and register a WebAuthn credential.
- tfa := &auth_model.TwoFactor{UID: uid}
- require.NoError(t, tfa.SetSecret("test-secret"))
- require.NoError(t, auth_model.NewTwoFactor(ctx, tfa))
- _, err := auth_model.CreateCredential(ctx, uid, "test-key", &webauthn.Credential{ID: []byte("test-cred-id")})
- require.NoError(t, err)
-
- has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
- require.NoError(t, err)
- require.True(t, has)
-
- // Both records are removed and counted separately.
- totp, webAuthn, err := auth_model.DisableTwoFactor(ctx, uid)
- require.NoError(t, err)
- assert.EqualValues(t, 1, totp)
- assert.EqualValues(t, 1, webAuthn)
-
- has, err = auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
- require.NoError(t, err)
- assert.False(t, has)
-
- // A second call on a user without 2FA is a no-op.
- totp, webAuthn, err = auth_model.DisableTwoFactor(ctx, uid)
- require.NoError(t, err)
- assert.EqualValues(t, 0, totp)
- assert.EqualValues(t, 0, webAuthn)
+ // an invalid passcode is rejected without consuming anything
+ ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), "000000")
+ require.NoError(t, err)
+ assert.False(t, ok)
+ })
+
+ t.Run("DisableTwoFactor", func(t *testing.T) {
+ ctx := t.Context()
+
+ const uid = 1000 // a uid with no user/2FA fixtures
+
+ // Enroll TOTP and register a WebAuthn credential.
+ tfa := &auth_model.TwoFactor{UID: uid}
+ require.NoError(t, tfa.SetSecret("test-secret"))
+ require.NoError(t, auth_model.NewTwoFactor(ctx, tfa))
+ _, err := auth_model.CreateCredential(ctx, uid, "test-key", &webauthn.Credential{ID: []byte("test-cred-id")})
+ require.NoError(t, err)
+
+ has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
+ require.NoError(t, err)
+ require.True(t, has)
+
+ // Both records are removed and counted separately.
+ totp, webAuthn, err := auth_model.DisableTwoFactor(ctx, uid)
+ require.NoError(t, err)
+ assert.EqualValues(t, 1, totp)
+ assert.EqualValues(t, 1, webAuthn)
+
+ has, err = auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
+ require.NoError(t, err)
+ assert.False(t, has)
+
+ // A second call on a user without 2FA is a no-op.
+ totp, webAuthn, err = auth_model.DisableTwoFactor(ctx, uid)
+ require.NoError(t, err)
+ assert.EqualValues(t, 0, totp)
+ assert.EqualValues(t, 0, webAuthn)
+ })
}
diff --git a/models/auth/webauthn_test.go b/models/auth/webauthn_test.go
index 81eeacf1e08..1e6817353be 100644
--- a/models/auth/webauthn_test.go
+++ b/models/auth/webauthn_test.go
@@ -13,29 +13,38 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestGetWebAuthnCredentialByID(t *testing.T) {
+func TestWebAuthnCredentials(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 1)
- assert.NoError(t, err)
- assert.Equal(t, "WebAuthn credential", res.Name)
+ t.Run("GetWebAuthnCredentialByID", func(t *testing.T) {
+ res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 1)
+ assert.NoError(t, err)
+ assert.Equal(t, "WebAuthn credential", res.Name)
- _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 99999, 1)
- assert.Error(t, err)
- assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
+ _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 99999, 1)
+ assert.Error(t, err)
+ assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
- _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 99999)
- assert.Error(t, err)
- assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
-}
+ _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 99999)
+ assert.Error(t, err)
+ assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
+ })
-func TestGetWebAuthnCredentialsByUID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("GetWebAuthnCredentialsByUID", func(t *testing.T) {
+ res, err := auth_model.GetWebAuthnCredentialsByUID(t.Context(), 32)
+ assert.NoError(t, err)
+ assert.Len(t, res, 1)
+ assert.Equal(t, "WebAuthn credential", res[0].Name)
+ })
- res, err := auth_model.GetWebAuthnCredentialsByUID(t.Context(), 32)
- assert.NoError(t, err)
- assert.Len(t, res, 1)
- assert.Equal(t, "WebAuthn credential", res[0].Name)
+ t.Run("CreateCredential", func(t *testing.T) {
+ res, err := auth_model.CreateCredential(t.Context(), 1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")})
+ assert.NoError(t, err)
+ assert.Equal(t, "WebAuthn Created Credential", res.Name)
+ assert.Equal(t, []byte("Test"), res.CredentialID)
+
+ unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1})
+ })
}
func TestWebAuthnCredential_TableName(t *testing.T) {
@@ -43,28 +52,11 @@ func TestWebAuthnCredential_TableName(t *testing.T) {
}
func TestWebAuthnCredential_UpdateSignCount(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1})
- cred.SignCount = 1
- assert.NoError(t, cred.UpdateSignCount(t.Context()))
- unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1, SignCount: 1})
-}
-
-func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1})
- cred.SignCount = 0xffffffff
- assert.NoError(t, cred.UpdateSignCount(t.Context()))
- unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1, SignCount: 0xffffffff})
-}
-
-func TestCreateCredential(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- res, err := auth_model.CreateCredential(t.Context(), 1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")})
- assert.NoError(t, err)
- assert.Equal(t, "WebAuthn Created Credential", res.Name)
- assert.Equal(t, []byte("Test"), res.CredentialID)
-
- unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1})
+ for _, signCount := range []uint32{1, 0xffffffff} {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1})
+ cred.SignCount = signCount
+ assert.NoError(t, cred.UpdateSignCount(t.Context()))
+ unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1, SignCount: signCount})
+ }
}
diff --git a/models/db/context_test.go b/models/db/context_test.go
index c409f5bbdab..880c740500c 100644
--- a/models/db/context_test.go
+++ b/models/db/context_test.go
@@ -42,7 +42,10 @@ func TestTxContext(t *testing.T) {
assert.NoError(t, committer.Commit())
}
- { // reuse the transaction created by TxContext and commit it
+ for _, finish := range []func(db.Committer) error{
+ db.Committer.Commit, // reuse the transaction created by TxContext and commit it
+ db.Committer.Close, // reuse the transaction created by TxContext and close it
+ } {
ctx, committer, err := db.TxContext(t.Context())
engine := db.GetEngine(ctx)
assert.NoError(t, err)
@@ -52,24 +55,9 @@ func TestTxContext(t *testing.T) {
assert.NoError(t, err)
assert.True(t, db.InTransaction(ctx))
assert.Equal(t, engine, db.GetEngine(ctx))
- assert.NoError(t, committer.Commit())
+ assert.NoError(t, finish(committer))
}
- assert.NoError(t, committer.Commit())
- }
-
- { // reuse the transaction created by TxContext and close it
- ctx, committer, err := db.TxContext(t.Context())
- engine := db.GetEngine(ctx)
- assert.NoError(t, err)
- assert.True(t, db.InTransaction(ctx))
- {
- ctx, committer, err := db.TxContext(ctx)
- assert.NoError(t, err)
- assert.True(t, db.InTransaction(ctx))
- assert.Equal(t, engine, db.GetEngine(ctx))
- assert.NoError(t, committer.Close())
- }
- assert.NoError(t, committer.Close())
+ assert.NoError(t, finish(committer))
}
{ // reuse the transaction created by WithTx
diff --git a/models/db/index_test.go b/models/db/index_test.go
index 5dece7e1beb..e1cf937e746 100644
--- a/models/db/index_test.go
+++ b/models/db/index_test.go
@@ -35,27 +35,22 @@ func TestSyncMaxResourceIndex(t *testing.T) {
xe := unittest.GetXORMEngine()
assert.NoError(t, xe.Sync(&TestIndex{}))
- err := db.SyncMaxResourceIndex(t.Context(), "test_index", 10, 51)
- assert.NoError(t, err)
-
- // sync new max index
- maxIndex, err := getCurrentResourceIndex(t.Context(), "test_index", 10)
- assert.NoError(t, err)
- assert.EqualValues(t, 51, maxIndex)
-
- // smaller index doesn't change
- err = db.SyncMaxResourceIndex(t.Context(), "test_index", 10, 30)
- assert.NoError(t, err)
- maxIndex, err = getCurrentResourceIndex(t.Context(), "test_index", 10)
- assert.NoError(t, err)
- assert.EqualValues(t, 51, maxIndex)
-
- // larger index changes
- err = db.SyncMaxResourceIndex(t.Context(), "test_index", 10, 62)
- assert.NoError(t, err)
- maxIndex, err = getCurrentResourceIndex(t.Context(), "test_index", 10)
- assert.NoError(t, err)
- assert.EqualValues(t, 62, maxIndex)
+ var maxIndex int64
+ var err error
+ for _, test := range []struct {
+ index int64
+ expected int
+ }{
+ {51, 51}, // sync new max index
+ {30, 51}, // smaller index doesn't change
+ {62, 62}, // larger index changes
+ } {
+ err = db.SyncMaxResourceIndex(t.Context(), "test_index", 10, test.index)
+ assert.NoError(t, err)
+ maxIndex, err = getCurrentResourceIndex(t.Context(), "test_index", 10)
+ assert.NoError(t, err)
+ assert.EqualValues(t, test.expected, maxIndex)
+ }
// commit transaction
err = db.WithTx(t.Context(), func(ctx context.Context) error {
diff --git a/models/dbfs/dbfs_test.go b/models/dbfs/dbfs_test.go
index 63ea3feffb9..6a5f4534632 100644
--- a/models/dbfs/dbfs_test.go
+++ b/models/dbfs/dbfs_test.go
@@ -54,38 +54,29 @@ func TestDbfsBasic(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "0bcdefghi9", string(buf))
- // write to new size
- _, err = f.Seek(-1, io.SeekEnd)
- assert.NoError(t, err)
- _, err = f.Write([]byte("JKLMNOP")) // blocks: 0bcd efgh iJKL MNOP
- assert.NoError(t, err)
- _, err = f.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- buf, err = io.ReadAll(f)
- assert.NoError(t, err)
- assert.Equal(t, "0bcdefghiJKLMNOP", string(buf))
-
- // write beyond EOF and fill with zero
- _, err = f.Seek(5, io.SeekCurrent)
- assert.NoError(t, err)
- _, err = f.Write([]byte("xyzu")) // blocks: 0bcd efgh iJKL MNOP 0000 0xyz u
- assert.NoError(t, err)
- _, err = f.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- buf, err = io.ReadAll(f)
- assert.NoError(t, err)
- assert.Equal(t, "0bcdefghiJKLMNOP\x00\x00\x00\x00\x00xyzu", string(buf))
-
- // write to the block with zeros
- _, err = f.Seek(-6, io.SeekCurrent)
- assert.NoError(t, err)
- _, err = f.Write([]byte("ABCD")) // blocks: 0bcd efgh iJKL MNOP 000A BCDz u
- assert.NoError(t, err)
- _, err = f.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- buf, err = io.ReadAll(f)
- assert.NoError(t, err)
- assert.Equal(t, "0bcdefghiJKLMNOP\x00\x00\x00ABCDzu", string(buf))
+ for _, write := range []struct {
+ offset int64
+ whence int
+ content string
+ expected string
+ }{
+ // write to new size
+ {-1, io.SeekEnd, "JKLMNOP", "0bcdefghiJKLMNOP"}, // blocks: 0bcd efgh iJKL MNOP
+ // write beyond EOF and fill with zero
+ {5, io.SeekCurrent, "xyzu", "0bcdefghiJKLMNOP\x00\x00\x00\x00\x00xyzu"}, // blocks: 0bcd efgh iJKL MNOP 0000 0xyz u
+ // write to the block with zeros
+ {-6, io.SeekCurrent, "ABCD", "0bcdefghiJKLMNOP\x00\x00\x00ABCDzu"}, // blocks: 0bcd efgh iJKL MNOP 000A BCDz u
+ } {
+ _, err = f.Seek(write.offset, write.whence)
+ assert.NoError(t, err)
+ _, err = f.Write([]byte(write.content))
+ assert.NoError(t, err)
+ _, err = f.Seek(0, io.SeekStart)
+ assert.NoError(t, err)
+ buf, err = io.ReadAll(f)
+ assert.NoError(t, err)
+ assert.Equal(t, write.expected, string(buf))
+ }
assert.NoError(t, f.Close())
@@ -121,17 +112,11 @@ func TestDbfsBasic(t *testing.T) {
assert.EqualValues(t, 10, stat.Size())
t.Run("NonExisting", func(t *testing.T) {
- f, err := OpenFile(t.Context(), "non-existing.txt", os.O_RDONLY)
- assert.ErrorIs(t, err, os.ErrNotExist)
- assert.Nil(t, f)
-
- f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY)
- assert.ErrorIs(t, err, os.ErrNotExist)
- assert.Nil(t, f)
-
- f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY|os.O_APPEND|os.O_TRUNC)
- assert.ErrorIs(t, err, os.ErrNotExist)
- assert.Nil(t, f)
+ for _, flags := range []int{os.O_RDONLY, os.O_WRONLY, os.O_WRONLY | os.O_APPEND | os.O_TRUNC} {
+ f, err := OpenFile(t.Context(), "non-existing.txt", flags)
+ assert.ErrorIs(t, err, os.ErrNotExist)
+ assert.Nil(t, f)
+ }
})
t.Run("Existing", func(t *testing.T) {
@@ -211,17 +196,12 @@ func TestDbfsSeekWrite(t *testing.T) {
n, err := fw.Write([]byte("111"))
assert.NoError(t, err)
- _, err = fw.Seek(int64(n), io.SeekStart)
- assert.NoError(t, err)
-
- _, err = fw.Write([]byte("222"))
- assert.NoError(t, err)
-
- _, err = fw.Seek(int64(n), io.SeekStart)
- assert.NoError(t, err)
-
- _, err = fw.Write([]byte("333"))
- assert.NoError(t, err)
+ for _, content := range []string{"222", "333"} {
+ _, err = fw.Seek(int64(n), io.SeekStart)
+ assert.NoError(t, err)
+ _, err = fw.Write([]byte(content))
+ assert.NoError(t, err)
+ }
// then read it
fr, err := OpenFile(t.Context(), "test2.log", os.O_RDONLY)
diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go
index 8b1c250cf28..74e2e21762b 100644
--- a/models/git/commit_status_test.go
+++ b/models/git/commit_status_test.go
@@ -92,116 +92,45 @@ func TestGetCommitStatuses(t *testing.T) {
func Test_CalcCommitStatus(t *testing.T) {
kases := []struct {
- statuses []*git_model.CommitStatus
- expected *git_model.CommitStatus
+ statuses commitstatus.CommitStatusStates
+ expected commitstatus.CommitStatusState
}{
{
- statuses: []*git_model.CommitStatus{
- {
- State: commitstatus.CommitStatusPending,
- },
- },
- expected: &git_model.CommitStatus{
- State: commitstatus.CommitStatusPending,
- },
+ statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusPending},
+ expected: commitstatus.CommitStatusPending,
},
{
- statuses: []*git_model.CommitStatus{
- {
- State: commitstatus.CommitStatusSuccess,
- },
- {
- State: commitstatus.CommitStatusPending,
- },
- },
- expected: &git_model.CommitStatus{
- State: commitstatus.CommitStatusPending,
- },
+ statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusSuccess, commitstatus.CommitStatusPending},
+ expected: commitstatus.CommitStatusPending,
},
{
- statuses: []*git_model.CommitStatus{
- {
- State: commitstatus.CommitStatusSuccess,
- },
- {
- State: commitstatus.CommitStatusPending,
- },
- {
- State: commitstatus.CommitStatusSuccess,
- },
- },
- expected: &git_model.CommitStatus{
- State: commitstatus.CommitStatusPending,
- },
+ statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusSuccess, commitstatus.CommitStatusPending, commitstatus.CommitStatusSuccess},
+ expected: commitstatus.CommitStatusPending,
},
{
- statuses: []*git_model.CommitStatus{
- {
- State: commitstatus.CommitStatusError,
- },
- {
- State: commitstatus.CommitStatusPending,
- },
- {
- State: commitstatus.CommitStatusSuccess,
- },
- },
- expected: &git_model.CommitStatus{
- State: commitstatus.CommitStatusFailure,
- },
+ statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusError, commitstatus.CommitStatusPending, commitstatus.CommitStatusSuccess},
+ expected: commitstatus.CommitStatusFailure,
},
{
- statuses: []*git_model.CommitStatus{
- {
- State: commitstatus.CommitStatusWarning,
- },
- {
- State: commitstatus.CommitStatusPending,
- },
- {
- State: commitstatus.CommitStatusSuccess,
- },
- },
- expected: &git_model.CommitStatus{
- State: commitstatus.CommitStatusFailure,
- },
+ statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusWarning, commitstatus.CommitStatusPending, commitstatus.CommitStatusSuccess},
+ expected: commitstatus.CommitStatusFailure,
},
{
- statuses: []*git_model.CommitStatus{
- {
- State: commitstatus.CommitStatusSuccess,
- },
- {
- State: commitstatus.CommitStatusSuccess,
- },
- {
- State: commitstatus.CommitStatusSuccess,
- },
- },
- expected: &git_model.CommitStatus{
- State: commitstatus.CommitStatusSuccess,
- },
+ statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusSuccess, commitstatus.CommitStatusSuccess, commitstatus.CommitStatusSuccess},
+ expected: commitstatus.CommitStatusSuccess,
},
{
- statuses: []*git_model.CommitStatus{
- {
- State: commitstatus.CommitStatusFailure,
- },
- {
- State: commitstatus.CommitStatusError,
- },
- {
- State: commitstatus.CommitStatusWarning,
- },
- },
- expected: &git_model.CommitStatus{
- State: commitstatus.CommitStatusFailure,
- },
+ statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusFailure, commitstatus.CommitStatusError, commitstatus.CommitStatusWarning},
+ expected: commitstatus.CommitStatusFailure,
},
}
for _, kase := range kases {
- assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses), "statuses: %v", kase.statuses)
+ statuses := make([]*git_model.CommitStatus, len(kase.statuses))
+ for i, state := range kase.statuses {
+ statuses[i] = &git_model.CommitStatus{State: state}
+ }
+ assert.Equal(t, &git_model.CommitStatus{State: kase.expected}, git_model.CalcCommitStatus(statuses), "statuses: %v", statuses)
}
}
@@ -226,29 +155,19 @@ func TestFindRepoRecentCommitStatusContexts(t *testing.T) {
assert.NoError(t, err)
}()
- err = git_model.NewCommitStatus(t.Context(), git_model.NewCommitStatusOptions{
- Repo: repo2,
- Creator: user2,
- SHA: commit.ID,
- CommitStatus: &git_model.CommitStatus{
- State: commitstatus.CommitStatusFailure,
- TargetURL: "https://example.com/tests/",
- Context: "compliance/lint-backend",
- },
- })
- assert.NoError(t, err)
-
- err = git_model.NewCommitStatus(t.Context(), git_model.NewCommitStatusOptions{
- Repo: repo2,
- Creator: user2,
- SHA: commit.ID,
- CommitStatus: &git_model.CommitStatus{
- State: commitstatus.CommitStatusSuccess,
- TargetURL: "https://example.com/tests/",
- Context: "compliance/lint-backend",
- },
- })
- assert.NoError(t, err)
+ for _, state := range []commitstatus.CommitStatusState{commitstatus.CommitStatusFailure, commitstatus.CommitStatusSuccess} {
+ err = git_model.NewCommitStatus(t.Context(), git_model.NewCommitStatusOptions{
+ Repo: repo2,
+ Creator: user2,
+ SHA: commit.ID,
+ CommitStatus: &git_model.CommitStatus{
+ State: state,
+ TargetURL: "https://example.com/tests/",
+ Context: "compliance/lint-backend",
+ },
+ })
+ assert.NoError(t, err)
+ }
contexts, err := git_model.FindRepoRecentCommitStatusContexts(t.Context(), repo2.ID, time.Hour)
assert.NoError(t, err)
diff --git a/models/git/protected_branch_list_test.go b/models/git/protected_branch_list_test.go
index 298c2fa0741..d22a1950712 100644
--- a/models/git/protected_branch_list_test.go
+++ b/models/git/protected_branch_list_test.go
@@ -75,65 +75,34 @@ func TestBranchRuleMatchPriority(t *testing.T) {
}
}
-func TestBranchRuleSortLegacy(t *testing.T) {
- in := []*ProtectedBranch{{
- RuleName: "b",
- CreatedUnix: 1,
- }, {
- RuleName: "b/*",
- CreatedUnix: 3,
- }, {
- RuleName: "a/*",
- CreatedUnix: 2,
- }, {
- RuleName: "c",
- CreatedUnix: 0,
- }, {
- RuleName: "a",
- CreatedUnix: 4,
- }}
- expect := []string{"c", "b", "a", "a/*", "b/*"}
-
- pbr := ProtectedBranchRules(in)
- pbr.sort()
-
- var got []string
- for i := range pbr {
- got = append(got, pbr[i].RuleName)
+func TestBranchRuleSort(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ rules ProtectedBranchRules
+ expected []string
+ }{
+ {"Legacy", ProtectedBranchRules{
+ {RuleName: "b", CreatedUnix: 1},
+ {RuleName: "b/*", CreatedUnix: 3},
+ {RuleName: "a/*", CreatedUnix: 2},
+ {RuleName: "c", CreatedUnix: 0},
+ {RuleName: "a", CreatedUnix: 4},
+ }, []string{"c", "b", "a", "a/*", "b/*"}},
+ {"Priority", ProtectedBranchRules{
+ {RuleName: "b", CreatedUnix: 1, Priority: 4},
+ {RuleName: "b/*", CreatedUnix: 3, Priority: 2},
+ {RuleName: "a/*", CreatedUnix: 2, Priority: 1},
+ {RuleName: "c", CreatedUnix: 0, Priority: 0},
+ {RuleName: "a", CreatedUnix: 4, Priority: 3},
+ }, []string{"c", "a/*", "b/*", "a", "b"}},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ tc.rules.sort()
+ var got []string
+ for i := range tc.rules {
+ got = append(got, tc.rules[i].RuleName)
+ }
+ assert.Equal(t, tc.expected, got)
+ })
}
- assert.Equal(t, expect, got)
-}
-
-func TestBranchRuleSortPriority(t *testing.T) {
- in := []*ProtectedBranch{{
- RuleName: "b",
- CreatedUnix: 1,
- Priority: 4,
- }, {
- RuleName: "b/*",
- CreatedUnix: 3,
- Priority: 2,
- }, {
- RuleName: "a/*",
- CreatedUnix: 2,
- Priority: 1,
- }, {
- RuleName: "c",
- CreatedUnix: 0,
- Priority: 0,
- }, {
- RuleName: "a",
- CreatedUnix: 4,
- Priority: 3,
- }}
- expect := []string{"c", "a/*", "b/*", "a", "b"}
-
- pbr := ProtectedBranchRules(in)
- pbr.sort()
-
- var got []string
- for i := range pbr {
- got = append(got, pbr[i].RuleName)
- }
- assert.Equal(t, expect, got)
}
diff --git a/models/git/protected_branch_test.go b/models/git/protected_branch_test.go
index 32133860104..5ac0a1dc747 100644
--- a/models/git/protected_branch_test.go
+++ b/models/git/protected_branch_test.go
@@ -85,194 +85,202 @@ func TestBranchRuleMatch(t *testing.T) {
}
}
-func TestUpdateProtectBranchPriorities(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+func TestProtectedBranchPermissionsAndPriorities(t *testing.T) {
+ setupErr := unittest.PrepareTestDatabase()
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ t.Run("CanBypassBranchProtection", func(t *testing.T) {
+ assert.NoError(t, setupErr)
- // Create some test protected branches with initial priorities
- protectedBranches := []*ProtectedBranch{
- {
- RepoID: repo.ID,
- RuleName: "master",
- Priority: 1,
- },
- {
- RepoID: repo.ID,
- RuleName: "develop",
- Priority: 2,
- },
- {
- RepoID: repo.ID,
- RuleName: "feature/*",
- Priority: 3,
- },
- }
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) // not in team 1
+ teamMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- for _, pb := range protectedBranches {
- _, err := db.GetEngine(t.Context()).Insert(pb)
+ pb := &ProtectedBranch{
+ EnableBypassAllowlist: true,
+ BypassAllowlistUserIDs: []int64{user.ID},
+ }
+
+ testBypass := func(t *testing.T, expected bool, pb *ProtectedBranch, doer *user_model.User, isAdmin bool) {
+ assert.Equal(t, expected, CanBypassBranchProtection(t.Context(), pb, doer, isAdmin))
+ }
+ // User bypasses via explicit allowlist.
+ testBypass(t, true, pb, user, false)
+
+ // Non-admin cannot bypass when allowlist is disabled.
+ pb.EnableBypassAllowlist = false
+ testBypass(t, false, pb, user, false)
+
+ // Repo admin can bypass independently of allowlist when not blocked.
+ testBypass(t, true, pb, user, true)
+
+ // Admin override block still allows bypass for allowlisted users.
+ pb.EnableBypassAllowlist = true
+ pb.BlockAdminMergeOverride = true
+ testBypass(t, true, pb, user, false)
+
+ // admin cannot bypass without allowlist membership.
+ pb.BypassAllowlistUserIDs = nil
+ testBypass(t, false, pb, user, true)
+
+ // admin can bypass when allowlisted.
+ pb.BypassAllowlistUserIDs = []int64{user.ID}
+ testBypass(t, true, pb, user, true)
+
+ // User bypasses via team allowlist membership.
+ pb.EnableBypassAllowlist = true
+ pb.BlockAdminMergeOverride = false
+ pb.BypassAllowlistUserIDs = nil
+ pb.BypassAllowlistTeamIDs = []int64{1} // team 1 contains user 2 in test fixtures
+ testBypass(t, true, pb, teamMember, false)
+
+ // User does not bypass when not in allowlisted teams.
+ testBypass(t, false, pb, user, false)
+ })
+
+ t.Run("ProtectedBranchCanUserDelete", func(t *testing.T) {
+ require.NoError(t, setupErr)
+ writePermission := access_model.Permission{}
+ writePermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeWrite)
+ readPermission := access_model.Permission{}
+ readPermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeRead)
+
+ for _, user := range []*user_model.User{{ID: 2, Name: "user2"}, user_model.NewActionsUser(), user_model.NewDeployKeyUser()} {
+ t.Run(user.GetDisplayName(), func(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ config ProtectedBranchConfig
+ want bool
+ }{
+ {name: "disabled by default", config: ProtectedBranchConfig{CanPush: true}},
+ {name: "push disabled", config: ProtectedBranchConfig{CanDelete: true}},
+ {name: "unrestricted", config: ProtectedBranchConfig{CanPush: true, CanDelete: true}, want: true},
+ {name: "empty push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true}},
+ {name: "not in push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{1}}},
+ {name: "empty deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true}},
+ {name: "not in deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{1}}},
+ {name: "in both allowlists", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{user.ID}, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}, want: true},
+ {name: "deletion allowlist does not grant push", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}},
+ {name: "team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{1}}, want: user.ID == 2},
+ {name: "push team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistTeamIDs: []int64{1}}, want: user.ID == 2},
+ {name: "not in team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{3}}},
+ {name: "user or team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}, DeletionAllowlistTeamIDs: []int64{3}}, want: true},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ pb := &ProtectedBranch{ProtectedBranchConfig: tt.config}
+ assert.Equal(t, tt.want, pb.CanUserDelete(t.Context(), user, writePermission))
+ assert.False(t, pb.CanUserDelete(t.Context(), user, readPermission))
+ assert.False(t, pb.CanUserDelete(t.Context(), nil, writePermission))
+ })
+ }
+ })
+ }
+ })
+
+ t.Run("UpdateProtectBranchPriorities", func(t *testing.T) {
+ assert.NoError(t, setupErr)
+
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+
+ // Create some test protected branches with initial priorities
+ protectedBranches := []*ProtectedBranch{
+ {
+ RepoID: repo.ID,
+ RuleName: "master",
+ Priority: 1,
+ },
+ {
+ RepoID: repo.ID,
+ RuleName: "develop",
+ Priority: 2,
+ },
+ {
+ RepoID: repo.ID,
+ RuleName: "feature/*",
+ Priority: 3,
+ },
+ }
+
+ for _, pb := range protectedBranches {
+ _, err := db.GetEngine(t.Context()).Insert(pb)
+ assert.NoError(t, err)
+ }
+
+ // Test updating priorities
+ newPriorities := []int64{protectedBranches[2].ID, protectedBranches[0].ID, protectedBranches[1].ID}
+ err := UpdateProtectBranchPriorities(t.Context(), repo, newPriorities)
assert.NoError(t, err)
- }
- // Test updating priorities
- newPriorities := []int64{protectedBranches[2].ID, protectedBranches[0].ID, protectedBranches[1].ID}
- err := UpdateProtectBranchPriorities(t.Context(), repo, newPriorities)
- assert.NoError(t, err)
+ // Verify new priorities
+ pbs, err := FindRepoProtectedBranchRules(t.Context(), repo.ID)
+ assert.NoError(t, err)
- // Verify new priorities
- pbs, err := FindRepoProtectedBranchRules(t.Context(), repo.ID)
- assert.NoError(t, err)
+ expectedPriorities := map[string]int64{
+ "feature/*": 1,
+ "master": 2,
+ "develop": 3,
+ }
- expectedPriorities := map[string]int64{
- "feature/*": 1,
- "master": 2,
- "develop": 3,
- }
-
- for _, pb := range pbs {
- assert.Equal(t, expectedPriorities[pb.RuleName], pb.Priority)
- }
+ for _, pb := range pbs {
+ assert.Equal(t, expectedPriorities[pb.RuleName], pb.Priority)
+ }
+ })
}
func TestNewProtectBranchPriority(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ setupErr := unittest.PrepareTestDatabase()
- err := UpdateProtectBranch(t.Context(), repo, &ProtectedBranch{
- RepoID: repo.ID,
- RuleName: "branch-1",
- Priority: 1,
- }, WhitelistOptions{})
- assert.NoError(t, err)
+ t.Run("NewProtectBranchPriority", func(t *testing.T) {
+ assert.NoError(t, setupErr)
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- newPB := &ProtectedBranch{
- RepoID: repo.ID,
- RuleName: "branch-2",
- // Priority intentionally omitted
- }
+ err := UpdateProtectBranch(t.Context(), repo, &ProtectedBranch{
+ RepoID: repo.ID,
+ RuleName: "branch-1",
+ Priority: 1,
+ }, WhitelistOptions{})
+ assert.NoError(t, err)
- err = UpdateProtectBranch(t.Context(), repo, newPB, WhitelistOptions{})
- assert.NoError(t, err)
+ newPB := &ProtectedBranch{
+ RepoID: repo.ID,
+ RuleName: "branch-2",
+ // Priority intentionally omitted
+ }
- savedPB2, err := GetFirstMatchProtectedBranchRule(t.Context(), repo.ID, "branch-2")
- assert.NoError(t, err)
- assert.Equal(t, int64(2), savedPB2.Priority)
-}
-
-func TestCanBypassBranchProtection(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) // not in team 1
- teamMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
-
- pb := &ProtectedBranch{
- EnableBypassAllowlist: true,
- BypassAllowlistUserIDs: []int64{user.ID},
- }
-
- testBypass := func(t *testing.T, expected bool, pb *ProtectedBranch, doer *user_model.User, isAdmin bool) {
- assert.Equal(t, expected, CanBypassBranchProtection(t.Context(), pb, doer, isAdmin))
- }
- // User bypasses via explicit allowlist.
- testBypass(t, true, pb, user, false)
-
- // Non-admin cannot bypass when allowlist is disabled.
- pb.EnableBypassAllowlist = false
- testBypass(t, false, pb, user, false)
-
- // Repo admin can bypass independently of allowlist when not blocked.
- testBypass(t, true, pb, user, true)
-
- // Admin override block still allows bypass for allowlisted users.
- pb.EnableBypassAllowlist = true
- pb.BlockAdminMergeOverride = true
- testBypass(t, true, pb, user, false)
-
- // admin cannot bypass without allowlist membership.
- pb.BypassAllowlistUserIDs = nil
- testBypass(t, false, pb, user, true)
-
- // admin can bypass when allowlisted.
- pb.BypassAllowlistUserIDs = []int64{user.ID}
- testBypass(t, true, pb, user, true)
-
- // User bypasses via team allowlist membership.
- pb.EnableBypassAllowlist = true
- pb.BlockAdminMergeOverride = false
- pb.BypassAllowlistUserIDs = nil
- pb.BypassAllowlistTeamIDs = []int64{1} // team 1 contains user 2 in test fixtures
- testBypass(t, true, pb, teamMember, false)
-
- // User does not bypass when not in allowlisted teams.
- testBypass(t, false, pb, user, false)
-}
-
-func TestProtectedBranchConfig(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
- ctx := t.Context()
- pb := &ProtectedBranch{
- RepoID: 1, RuleName: "config-test",
- ProtectedBranchConfig: ProtectedBranchConfig{
- CanDelete: true, EnableDeletionAllowlist: true,
- DeletionAllowlistUserIDs: []int64{2, 4, user_model.ActionsUserID, user_model.DeployKeyUserID}, DeletionAllowlistTeamIDs: []int64{1, 2},
- CanPush: true, WhitelistUserIDs: []int64{2, 4}, WhitelistTeamIDs: []int64{1, 2}, StatusCheckContexts: []string{},
- },
- }
- require.NoError(t, db.Insert(ctx, pb))
- loaded := unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
- assert.Equal(t, pb.ProtectedBranchConfig, loaded.ProtectedBranchConfig)
- require.NoError(t, RemoveUserIDFromProtectedBranch(ctx, loaded, 2))
- require.NoError(t, RemoveTeamIDFromProtectedBranch(ctx, loaded, 1))
- loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
- assert.Equal(t, []int64{4}, loaded.WhitelistUserIDs)
- assert.Equal(t, []int64{2}, loaded.WhitelistTeamIDs)
- assert.True(t, loaded.CanPush)
- assert.True(t, loaded.CanDelete)
- assert.True(t, loaded.EnableDeletionAllowlist)
- assert.Equal(t, []int64{4, user_model.ActionsUserID, user_model.DeployKeyUserID}, loaded.DeletionAllowlistUserIDs)
- assert.Equal(t, []int64{2}, loaded.DeletionAllowlistTeamIDs)
- loaded.ProtectedBranchConfig = ProtectedBranchConfig{}
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- require.NoError(t, UpdateProtectBranch(ctx, repo, loaded, WhitelistOptions{}))
- loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
- assert.Equal(t, ProtectedBranchConfig{}, loaded.ProtectedBranchConfig)
-}
-
-func TestProtectedBranchCanUserDelete(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
- writePermission := access_model.Permission{}
- writePermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeWrite)
- readPermission := access_model.Permission{}
- readPermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeRead)
-
- for _, user := range []*user_model.User{{ID: 2, Name: "user2"}, user_model.NewActionsUser(), user_model.NewDeployKeyUser()} {
- t.Run(user.GetDisplayName(), func(t *testing.T) {
- for _, tt := range []struct {
- name string
- config ProtectedBranchConfig
- want bool
- }{
- {name: "disabled by default", config: ProtectedBranchConfig{CanPush: true}},
- {name: "push disabled", config: ProtectedBranchConfig{CanDelete: true}},
- {name: "unrestricted", config: ProtectedBranchConfig{CanPush: true, CanDelete: true}, want: true},
- {name: "empty push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true}},
- {name: "not in push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{1}}},
- {name: "empty deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true}},
- {name: "not in deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{1}}},
- {name: "in both allowlists", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{user.ID}, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}, want: true},
- {name: "deletion allowlist does not grant push", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}},
- {name: "team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{1}}, want: user.ID == 2},
- {name: "push team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistTeamIDs: []int64{1}}, want: user.ID == 2},
- {name: "not in team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{3}}},
- {name: "user or team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}, DeletionAllowlistTeamIDs: []int64{3}}, want: true},
- } {
- t.Run(tt.name, func(t *testing.T) {
- pb := &ProtectedBranch{ProtectedBranchConfig: tt.config}
- assert.Equal(t, tt.want, pb.CanUserDelete(t.Context(), user, writePermission))
- assert.False(t, pb.CanUserDelete(t.Context(), user, readPermission))
- assert.False(t, pb.CanUserDelete(t.Context(), nil, writePermission))
- })
- }
- })
- }
+ err = UpdateProtectBranch(t.Context(), repo, newPB, WhitelistOptions{})
+ assert.NoError(t, err)
+
+ savedPB2, err := GetFirstMatchProtectedBranchRule(t.Context(), repo.ID, "branch-2")
+ assert.NoError(t, err)
+ assert.Equal(t, int64(2), savedPB2.Priority)
+ })
+
+ t.Run("ProtectedBranchConfig", func(t *testing.T) {
+ require.NoError(t, setupErr)
+ ctx := t.Context()
+ pb := &ProtectedBranch{
+ RepoID: 1, RuleName: "config-test",
+ ProtectedBranchConfig: ProtectedBranchConfig{
+ CanDelete: true, EnableDeletionAllowlist: true,
+ DeletionAllowlistUserIDs: []int64{2, 4, user_model.ActionsUserID, user_model.DeployKeyUserID}, DeletionAllowlistTeamIDs: []int64{1, 2},
+ CanPush: true, WhitelistUserIDs: []int64{2, 4}, WhitelistTeamIDs: []int64{1, 2}, StatusCheckContexts: []string{},
+ },
+ }
+ require.NoError(t, db.Insert(ctx, pb))
+ loaded := unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
+ assert.Equal(t, pb.ProtectedBranchConfig, loaded.ProtectedBranchConfig)
+ require.NoError(t, RemoveUserIDFromProtectedBranch(ctx, loaded, 2))
+ require.NoError(t, RemoveTeamIDFromProtectedBranch(ctx, loaded, 1))
+ loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
+ assert.Equal(t, []int64{4}, loaded.WhitelistUserIDs)
+ assert.Equal(t, []int64{2}, loaded.WhitelistTeamIDs)
+ assert.True(t, loaded.CanPush)
+ assert.True(t, loaded.CanDelete)
+ assert.True(t, loaded.EnableDeletionAllowlist)
+ assert.Equal(t, []int64{4, user_model.ActionsUserID, user_model.DeployKeyUserID}, loaded.DeletionAllowlistUserIDs)
+ assert.Equal(t, []int64{2}, loaded.DeletionAllowlistTeamIDs)
+ loaded.ProtectedBranchConfig = ProtectedBranchConfig{}
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ require.NoError(t, UpdateProtectBranch(ctx, repo, loaded, WhitelistOptions{}))
+ loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
+ assert.Equal(t, ProtectedBranchConfig{}, loaded.ProtectedBranchConfig)
+ })
}
diff --git a/models/git/protected_tag_test.go b/models/git/protected_tag_test.go
index a0400c9bfa1..46d5dfc9ca3 100644
--- a/models/git/protected_tag_test.go
+++ b/models/git/protected_tag_test.go
@@ -15,44 +15,31 @@ import (
func TestIsUserAllowed(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- pt := &git_model.ProtectedTag{}
- allowed, err := git_model.IsUserAllowedModifyTag(t.Context(), pt, 1)
- assert.NoError(t, err)
- assert.False(t, allowed)
+ checkAllowed := func(pt *git_model.ProtectedTag, userID int64, check assert.BoolAssertionFunc) {
+ allowed, err := git_model.IsUserAllowedModifyTag(t.Context(), pt, userID)
+ assert.NoError(t, err)
+ check(t, allowed)
+ }
+ checkAllowed(&git_model.ProtectedTag{}, 1, assert.False)
- pt = &git_model.ProtectedTag{
+ pt := &git_model.ProtectedTag{
AllowlistUserIDs: []int64{1},
}
- allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 1)
- assert.NoError(t, err)
- assert.True(t, allowed)
-
- allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 2)
- assert.NoError(t, err)
- assert.False(t, allowed)
+ checkAllowed(pt, 1, assert.True)
+ checkAllowed(pt, 2, assert.False)
pt = &git_model.ProtectedTag{
AllowlistTeamIDs: []int64{1},
}
- allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 1)
- assert.NoError(t, err)
- assert.False(t, allowed)
-
- allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 2)
- assert.NoError(t, err)
- assert.True(t, allowed)
+ checkAllowed(pt, 1, assert.False)
+ checkAllowed(pt, 2, assert.True)
pt = &git_model.ProtectedTag{
AllowlistUserIDs: []int64{1},
AllowlistTeamIDs: []int64{1},
}
- allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 1)
- assert.NoError(t, err)
- assert.True(t, allowed)
-
- allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 2)
- assert.NoError(t, err)
- assert.True(t, allowed)
+ checkAllowed(pt, 1, assert.True)
+ checkAllowed(pt, 2, assert.True)
}
func TestIsUserAllowedToControlTag(t *testing.T) {
@@ -118,47 +105,33 @@ func TestIsUserAllowedToControlTag(t *testing.T) {
},
}
- t.Run("Glob", func(t *testing.T) {
- protectedTags := []*git_model.ProtectedTag{
- {
- NamePattern: `*gitea`,
- AllowlistUserIDs: []int64{1},
- },
- {
- NamePattern: `v-*`,
- AllowlistUserIDs: []int64{2},
- },
- {
- NamePattern: "release",
- },
- }
+ for _, tc := range []struct {
+ name string
+ patterns []string
+ }{
+ {"Glob", []string{`*gitea`, `v-*`, "release"}},
+ {"Regex", []string{`/gitea\z/`, `/\Av-/`, "/release/"}},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ protectedTags := []*git_model.ProtectedTag{
+ {
+ NamePattern: tc.patterns[0],
+ AllowlistUserIDs: []int64{1},
+ },
+ {
+ NamePattern: tc.patterns[1],
+ AllowlistUserIDs: []int64{2},
+ },
+ {
+ NamePattern: tc.patterns[2],
+ },
+ }
- for n, c := range cases {
- isAllowed, err := git_model.IsUserAllowedToControlTag(t.Context(), protectedTags, c.name, c.userid)
- assert.NoError(t, err)
- assert.Equal(t, c.allowed, isAllowed, "case %d: error should match", n)
- }
- })
-
- t.Run("Regex", func(t *testing.T) {
- protectedTags := []*git_model.ProtectedTag{
- {
- NamePattern: `/gitea\z/`,
- AllowlistUserIDs: []int64{1},
- },
- {
- NamePattern: `/\Av-/`,
- AllowlistUserIDs: []int64{2},
- },
- {
- NamePattern: "/release/",
- },
- }
-
- for n, c := range cases {
- isAllowed, err := git_model.IsUserAllowedToControlTag(t.Context(), protectedTags, c.name, c.userid)
- assert.NoError(t, err)
- assert.Equal(t, c.allowed, isAllowed, "case %d: error should match", n)
- }
- })
+ for n, c := range cases {
+ isAllowed, err := git_model.IsUserAllowedToControlTag(t.Context(), protectedTags, c.name, c.userid)
+ assert.NoError(t, err)
+ assert.Equal(t, c.allowed, isAllowed, "case %d: error should match", n)
+ }
+ })
+ }
}
diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go
index aa63956a011..0113151e9b2 100644
--- a/models/issues/comment_test.go
+++ b/models/issues/comment_test.go
@@ -19,162 +19,177 @@ import (
)
func TestCreateComment(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+ prepareErr := unittest.PrepareTestDatabase()
- issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{})
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
- doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+ t.Run("LoadAssigneeUserAndTeam_DeletedTeamBecomesGhostTeam", func(t *testing.T) {
+ assert.NoError(t, prepareErr)
- now := time.Now().Unix()
- comment, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{
- Type: issues_model.CommentTypeComment,
- Doer: doer,
- Repo: repo,
- Issue: issue,
- Content: "Hello",
- })
- assert.NoError(t, err)
- then := time.Now().Unix()
-
- assert.Equal(t, issues_model.CommentTypeComment, comment.Type)
- assert.Equal(t, "Hello", comment.Content)
- assert.Equal(t, issue.ID, comment.IssueID)
- assert.Equal(t, doer.ID, comment.PosterID)
- unittest.AssertInt64InRange(t, now, then, int64(comment.CreatedUnix))
- unittest.AssertExistsAndLoadBean(t, comment) // assert actually added to DB
-
- updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID})
- unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
- assert.Nil(t, comment.CommentMetaData)
-
- ctx := issues_model.WithProjectWorkflow(t.Context(), project_model.WorkflowEventItemClosed)
- comment, err = issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
- Type: issues_model.CommentTypeProjectColumn,
- Doer: doer,
- Repo: repo,
- Issue: issue,
- ProjectColumnID: 5,
- ProjectColumnTitle: "Done",
- ProjectTitle: "Board",
- })
- require.NoError(t, err)
- assert.Equal(t, &issues_model.CommentMetaData{
- ProjectColumnID: 5,
- ProjectColumnTitle: "Done",
- ProjectTitle: "Board",
- ProjectWorkflowEvent: project_model.WorkflowEventItemClosed,
- SpecialDoerName: issues_model.SpecialDoerNameProjectWorkflow,
- }, unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID}).CommentMetaData)
-}
-
-func TestLoadAssigneeUserAndTeam_DeletedTeamBecomesGhostTeam(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 15})
- comment := &issues_model.Comment{
- Type: issues_model.CommentTypeAssignees,
- IssueID: issue.ID,
- AssigneeTeamID: 999999, // non-existing team ID
- }
- assert.NoError(t, comment.LoadAssigneeUserAndTeam(t.Context()))
- assert.NotNil(t, comment.AssigneeTeam)
- assert.EqualValues(t, -1, comment.AssigneeTeam.ID)
-}
-
-func TestCommentListLoadAttributesMixedAssignees(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
- first := &issues_model.Comment{
- ID: 1, PosterID: 1, IssueID: 1, LabelID: 1, MilestoneID: 1, OldMilestoneID: 1, AssigneeID: 1, DependentIssueID: 2,
- }
- last := &issues_model.Comment{
- ID: 2, PosterID: 2, IssueID: 4, LabelID: 2, MilestoneID: 3, OldMilestoneID: 3, AssigneeID: 2, DependentIssueID: 1,
- }
- comments := issues_model.CommentList{
- {AssigneeTeamID: 8},
- {AssigneeID: 999999},
- first,
- }
- for i := range db.DefaultMaxInSize {
- id := unittest.NonexistentID - int64(i)
- comments = append(comments, &issues_model.Comment{
- ID: id, PosterID: id, IssueID: id, LabelID: id, MilestoneID: id, OldMilestoneID: id, AssigneeID: id, DependentIssueID: id,
- })
- }
- comments = append(comments, last)
-
- require.NoError(t, comments.LoadAttributes(t.Context()))
- assert.Nil(t, comments[0].Assignee)
- assert.Zero(t, comments[0].AssigneeID)
- assert.Equal(t, user_model.GhostUserID, comments[1].AssigneeID)
- assert.Equal(t, user_model.GhostUserID, comments[1].Assignee.ID)
- for _, comment := range []*issues_model.Comment{first, last} {
- require.NotNil(t, comment.Poster)
- assert.Equal(t, comment.PosterID, comment.Poster.ID)
- require.NotNil(t, comment.Issue)
- assert.Equal(t, comment.IssueID, comment.Issue.ID)
- require.NotNil(t, comment.Label)
- assert.Equal(t, comment.LabelID, comment.Label.ID)
- require.NotNil(t, comment.Milestone)
- assert.Equal(t, comment.MilestoneID, comment.Milestone.ID)
- require.NotNil(t, comment.OldMilestone)
- assert.Equal(t, comment.OldMilestoneID, comment.OldMilestone.ID)
- require.NotNil(t, comment.Assignee)
- assert.Equal(t, comment.AssigneeID, comment.Assignee.ID)
- require.NotNil(t, comment.DependentIssue)
- assert.Equal(t, comment.DependentIssueID, comment.DependentIssue.ID)
- require.NotNil(t, comment.DependentIssue.Repo)
- assert.Equal(t, comment.DependentIssue.RepoID, comment.DependentIssue.Repo.ID)
- require.Len(t, comment.Attachments, 2)
- for _, attachment := range comment.Attachments {
- assert.Equal(t, comment.ID, attachment.CommentID)
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 15})
+ comment := &issues_model.Comment{
+ Type: issues_model.CommentTypeAssignees,
+ IssueID: issue.ID,
+ AssigneeTeamID: 999999, // non-existing team ID
}
- }
- missing := comments[3]
- assert.Equal(t, user_model.GhostUserID, missing.Poster.ID)
- assert.Equal(t, user_model.GhostUserID, missing.Assignee.ID)
- assert.Nil(t, missing.Issue)
- assert.Nil(t, missing.Label)
- assert.Nil(t, missing.Milestone)
- assert.Nil(t, missing.OldMilestone)
- assert.Nil(t, missing.DependentIssue)
- assert.Empty(t, missing.Attachments)
-}
+ assert.NoError(t, comment.LoadAssigneeUserAndTeam(t.Context()))
+ assert.NotNil(t, comment.AssigneeTeam)
+ assert.EqualValues(t, -1, comment.AssigneeTeam.ID)
+ })
-func Test_UpdateCommentAttachment(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("CommentListLoadAttributesMixedAssignees", func(t *testing.T) {
+ require.NoError(t, prepareErr)
- comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1})
- issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
- attachment := repo_model.Attachment{
- RepoID: issue.RepoID, // must match the comment's repo, else the cross-repo guard rejects it
- Name: "test.txt",
- }
- assert.NoError(t, db.Insert(t.Context(), &attachment))
+ first := &issues_model.Comment{
+ ID: 1, PosterID: 1, IssueID: 1, LabelID: 1, MilestoneID: 1, OldMilestoneID: 1, AssigneeID: 1, DependentIssueID: 2,
+ }
+ last := &issues_model.Comment{
+ ID: 2, PosterID: 2, IssueID: 4, LabelID: 2, MilestoneID: 3, OldMilestoneID: 3, AssigneeID: 2, DependentIssueID: 1,
+ }
+ comments := issues_model.CommentList{
+ {AssigneeTeamID: 8},
+ {AssigneeID: 999999},
+ first,
+ }
+ for i := range db.DefaultMaxInSize {
+ id := unittest.NonexistentID - int64(i)
+ comments = append(comments, &issues_model.Comment{
+ ID: id, PosterID: id, IssueID: id, LabelID: id, MilestoneID: id, OldMilestoneID: id, AssigneeID: id, DependentIssueID: id,
+ })
+ }
+ comments = append(comments, last)
- err := issues_model.UpdateCommentAttachments(t.Context(), comment, []string{attachment.UUID})
- assert.NoError(t, err)
+ require.NoError(t, comments.LoadAttributes(t.Context()))
+ assert.Nil(t, comments[0].Assignee)
+ assert.Zero(t, comments[0].AssigneeID)
+ assert.Equal(t, user_model.GhostUserID, comments[1].AssigneeID)
+ assert.Equal(t, user_model.GhostUserID, comments[1].Assignee.ID)
+ for _, comment := range []*issues_model.Comment{first, last} {
+ require.NotNil(t, comment.Poster)
+ assert.Equal(t, comment.PosterID, comment.Poster.ID)
+ require.NotNil(t, comment.Issue)
+ assert.Equal(t, comment.IssueID, comment.Issue.ID)
+ require.NotNil(t, comment.Label)
+ assert.Equal(t, comment.LabelID, comment.Label.ID)
+ require.NotNil(t, comment.Milestone)
+ assert.Equal(t, comment.MilestoneID, comment.Milestone.ID)
+ require.NotNil(t, comment.OldMilestone)
+ assert.Equal(t, comment.OldMilestoneID, comment.OldMilestone.ID)
+ require.NotNil(t, comment.Assignee)
+ assert.Equal(t, comment.AssigneeID, comment.Assignee.ID)
+ require.NotNil(t, comment.DependentIssue)
+ assert.Equal(t, comment.DependentIssueID, comment.DependentIssue.ID)
+ require.NotNil(t, comment.DependentIssue.Repo)
+ assert.Equal(t, comment.DependentIssue.RepoID, comment.DependentIssue.Repo.ID)
+ require.Len(t, comment.Attachments, 2)
+ for _, attachment := range comment.Attachments {
+ assert.Equal(t, comment.ID, attachment.CommentID)
+ }
+ }
+ missing := comments[3]
+ assert.Equal(t, user_model.GhostUserID, missing.Poster.ID)
+ assert.Equal(t, user_model.GhostUserID, missing.Assignee.ID)
+ assert.Nil(t, missing.Issue)
+ assert.Nil(t, missing.Label)
+ assert.Nil(t, missing.Milestone)
+ assert.Nil(t, missing.OldMilestone)
+ assert.Nil(t, missing.DependentIssue)
+ assert.Empty(t, missing.Attachments)
+ })
- attachment2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: attachment.ID})
- assert.Equal(t, attachment.Name, attachment2.Name)
- assert.Equal(t, comment.ID, attachment2.CommentID)
- assert.Equal(t, comment.IssueID, attachment2.IssueID)
-}
+ t.Run("FetchCodeComments", func(t *testing.T) {
+ assert.NoError(t, prepareErr)
-func TestFetchCodeComments(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+ res, err := issues_model.FetchCodeComments(t.Context(), issue, user, false)
+ assert.NoError(t, err)
+ assert.Contains(t, res, "README.md")
+ assert.Contains(t, res["README.md"], int64(4))
+ assert.Len(t, res["README.md"][4], 1)
+ assert.Equal(t, int64(4), res["README.md"][4][0].ID)
- issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
- res, err := issues_model.FetchCodeComments(t.Context(), issue, user, false)
- assert.NoError(t, err)
- assert.Contains(t, res, "README.md")
- assert.Contains(t, res["README.md"], int64(4))
- assert.Len(t, res["README.md"][4], 1)
- assert.Equal(t, int64(4), res["README.md"][4][0].ID)
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ res, err = issues_model.FetchCodeComments(t.Context(), issue, user2, false)
+ assert.NoError(t, err)
+ assert.Len(t, res, 1)
+ })
- user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- res, err = issues_model.FetchCodeComments(t.Context(), issue, user2, false)
- assert.NoError(t, err)
- assert.Len(t, res, 1)
+ t.Run("UpdateCommentAttachment", func(t *testing.T) {
+ assert.NoError(t, prepareErr)
+
+ comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1})
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
+ attachment := repo_model.Attachment{
+ RepoID: issue.RepoID, // must match the comment's repo, else the cross-repo guard rejects it
+ Name: "test.txt",
+ }
+ assert.NoError(t, db.Insert(t.Context(), &attachment))
+
+ err := issues_model.UpdateCommentAttachments(t.Context(), comment, []string{attachment.UUID})
+ assert.NoError(t, err)
+
+ attachment2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: attachment.ID})
+ assert.Equal(t, attachment.Name, attachment2.Name)
+ assert.Equal(t, comment.ID, attachment2.CommentID)
+ assert.Equal(t, comment.IssueID, attachment2.IssueID)
+ })
+
+ t.Run("UpdateIssueNumComments", func(t *testing.T) {
+ assert.NoError(t, prepareErr)
+ issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+
+ assert.NoError(t, issues_model.UpdateIssueNumComments(t.Context(), issue2.ID))
+ issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+ assert.Equal(t, 1, issue2.NumComments)
+ })
+
+ t.Run("CreateComment", func(t *testing.T) {
+ assert.NoError(t, prepareErr)
+
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{})
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
+ doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+
+ now := time.Now().Unix()
+ comment, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{
+ Type: issues_model.CommentTypeComment,
+ Doer: doer,
+ Repo: repo,
+ Issue: issue,
+ Content: "Hello",
+ })
+ assert.NoError(t, err)
+ then := time.Now().Unix()
+
+ assert.Equal(t, issues_model.CommentTypeComment, comment.Type)
+ assert.Equal(t, "Hello", comment.Content)
+ assert.Equal(t, issue.ID, comment.IssueID)
+ assert.Equal(t, doer.ID, comment.PosterID)
+ unittest.AssertInt64InRange(t, now, then, int64(comment.CreatedUnix))
+ unittest.AssertExistsAndLoadBean(t, comment) // assert actually added to DB
+
+ updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID})
+ unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
+ assert.Nil(t, comment.CommentMetaData)
+
+ ctx := issues_model.WithProjectWorkflow(t.Context(), project_model.WorkflowEventItemClosed)
+ comment, err = issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
+ Type: issues_model.CommentTypeProjectColumn,
+ Doer: doer,
+ Repo: repo,
+ Issue: issue,
+ ProjectColumnID: 5,
+ ProjectColumnTitle: "Done",
+ ProjectTitle: "Board",
+ })
+ require.NoError(t, err)
+ assert.Equal(t, &issues_model.CommentMetaData{
+ ProjectColumnID: 5,
+ ProjectColumnTitle: "Done",
+ ProjectTitle: "Board",
+ ProjectWorkflowEvent: project_model.WorkflowEventItemClosed,
+ SpecialDoerName: issues_model.SpecialDoerNameProjectWorkflow,
+ }, unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID}).CommentMetaData)
+ })
}
func TestAsCommentType(t *testing.T) {
@@ -211,12 +226,3 @@ func TestMigrate_InsertIssueComments(t *testing.T) {
unittest.CheckConsistencyFor(t, &issues_model.Issue{})
}
-
-func Test_UpdateIssueNumComments(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
-
- assert.NoError(t, issues_model.UpdateIssueNumComments(t.Context(), issue2.ID))
- issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
- assert.Equal(t, 1, issue2.NumComments)
-}
diff --git a/models/issues/issue_list_test.go b/models/issues/issue_list_test.go
index 1d8f8febd62..864f370b82c 100644
--- a/models/issues/issue_list_test.go
+++ b/models/issues/issue_list_test.go
@@ -17,111 +17,117 @@ import (
)
func TestIssueList_LoadRepositories(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+ prepareErr := unittest.PrepareTestDatabase()
- issueList := issues_model.IssueList{
- unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}),
- unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}),
- unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}),
- }
+ t.Run("LoadRepositories", func(t *testing.T) {
+ assert.NoError(t, prepareErr)
- repos, err := issueList.LoadRepositories(t.Context())
- assert.NoError(t, err)
- assert.Len(t, repos, 2)
- for _, issue := range issueList {
- assert.Equal(t, issue.RepoID, issue.Repo.ID)
- }
-}
-
-func TestIssueList_LoadIsRead(t *testing.T) {
- // Regression: In("issue_id") was missing the issueIDs argument, causing
- // xorm to generate "0=1" and never mark any issue as read.
- require.NoError(t, unittest.PrepareTestDatabase())
-
- issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
- issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
-
- // Fixture: uid=1 has is_read=true on issue 1 only.
- issueList := issues_model.IssueList{issue1, issue2}
- require.NoError(t, issueList.LoadIsRead(t.Context(), 1))
-
- assert.True(t, issue1.IsRead, "issue 1 should be marked read for user 1")
- assert.False(t, issue2.IsRead, "issue 2 should not be marked read for user 1")
-}
-
-func TestIssueList_LoadAttributes(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
- defer test.MockVariableValue(&setting.Service.EnableTimetracking, true)()
- fixtureIssues := issues_model.IssueList{
- unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}),
- unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}),
- unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}),
- }
- issueList := issues_model.IssueList{fixtureIssues[0]}
- for i := range db.DefaultMaxInSize {
- issueList = append(issueList, &issues_model.Issue{ID: int64(1000 + i), RepoID: 1, IsPull: true})
- }
- issueList = append(issueList, fixtureIssues[1:]...)
-
- require.NoError(t, issueList.LoadAttributes(t.Context()))
- require.NoError(t, issueList.LoadAttachments(t.Context()))
- require.NoError(t, issueList.LoadComments(t.Context()))
- assert.Len(t, fixtureIssues[0].Labels, 1)
- assert.Len(t, fixtureIssues[1].Labels, 2)
- assert.Len(t, fixtureIssues[0].Assignees, 1)
- assert.Empty(t, fixtureIssues[1].Assignees)
- assert.Len(t, fixtureIssues[0].Attachments, 1)
- assert.Len(t, fixtureIssues[1].Attachments, 1)
- assert.Len(t, fixtureIssues[2].Attachments, 2)
- assert.Len(t, fixtureIssues[0].Comments, 3)
- assert.Len(t, fixtureIssues[1].Comments, 4)
- assert.Len(t, fixtureIssues[2].Comments, 1)
- for _, issue := range fixtureIssues {
- assert.Equal(t, issue.RepoID, issue.Repo.ID)
- for _, label := range issue.Labels {
- assert.Equal(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: label.ID}), label)
- unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID})
- }
- if issue.PosterID > 0 {
- assert.Equal(t, issue.PosterID, issue.Poster.ID)
- }
- if issue.AssigneeID > 0 {
- assert.Equal(t, issue.AssigneeID, issue.Assignee.ID)
- }
- if issue.MilestoneID > 0 {
- assert.Equal(t, issue.MilestoneID, issue.Milestone.ID)
- }
- if issue.IsPull {
- require.NotNil(t, issue.PullRequest)
- assert.Equal(t, issue.ID, issue.PullRequest.IssueID)
- assert.Same(t, issue, issue.PullRequest.Issue)
- }
- for _, attachment := range issue.Attachments {
- assert.Equal(t, issue.ID, attachment.IssueID)
- }
- for _, comment := range issue.Comments {
- assert.Equal(t, issue.ID, comment.IssueID)
- }
- if issue.ID == int64(1) {
- assert.Equal(t, int64(400), issue.TotalTrackedTime)
- }
- if issue.ID == 1 || issue.ID == 2 {
- require.Len(t, issue.Projects, 1)
- assert.Equal(t, int64(1), issue.Projects[0].ID)
- } else {
- assert.Empty(t, issue.Projects)
- }
- }
- assert.Equal(t, int64(3682), fixtureIssues[1].TotalTrackedTime)
-
- require.NoError(t, issueList.LoadDiscussComments(t.Context()))
- assert.Len(t, fixtureIssues[0].Comments, 2)
- assert.Empty(t, fixtureIssues[1].Comments)
- assert.Len(t, fixtureIssues[2].Comments, 1)
- for _, issue := range fixtureIssues {
- for _, comment := range issue.Comments {
- assert.Equal(t, issue.ID, comment.IssueID)
- assert.Equal(t, issues_model.CommentTypeComment, comment.Type)
- }
- }
+ issueList := issues_model.IssueList{
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}),
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}),
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}),
+ }
+
+ repos, err := issueList.LoadRepositories(t.Context())
+ assert.NoError(t, err)
+ assert.Len(t, repos, 2)
+ for _, issue := range issueList {
+ assert.Equal(t, issue.RepoID, issue.Repo.ID)
+ }
+ })
+
+ t.Run("LoadIsRead", func(t *testing.T) {
+ require.NoError(t, prepareErr)
+
+ // Regression: In("issue_id") was missing the issueIDs argument, causing
+ // xorm to generate "0=1" and never mark any issue as read.
+
+ issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
+ issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+
+ // Fixture: uid=1 has is_read=true on issue 1 only.
+ issueList := issues_model.IssueList{issue1, issue2}
+ require.NoError(t, issueList.LoadIsRead(t.Context(), 1))
+
+ assert.True(t, issue1.IsRead, "issue 1 should be marked read for user 1")
+ assert.False(t, issue2.IsRead, "issue 2 should not be marked read for user 1")
+ })
+
+ t.Run("LoadAttributes", func(t *testing.T) {
+ require.NoError(t, prepareErr)
+
+ defer test.MockVariableValue(&setting.Service.EnableTimetracking, true)()
+ fixtureIssues := issues_model.IssueList{
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}),
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}),
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}),
+ }
+ issueList := issues_model.IssueList{fixtureIssues[0]}
+ for i := range db.DefaultMaxInSize {
+ issueList = append(issueList, &issues_model.Issue{ID: int64(1000 + i), RepoID: 1, IsPull: true})
+ }
+ issueList = append(issueList, fixtureIssues[1:]...)
+
+ require.NoError(t, issueList.LoadAttributes(t.Context()))
+ require.NoError(t, issueList.LoadAttachments(t.Context()))
+ require.NoError(t, issueList.LoadComments(t.Context()))
+ assert.Len(t, fixtureIssues[0].Labels, 1)
+ assert.Len(t, fixtureIssues[1].Labels, 2)
+ assert.Len(t, fixtureIssues[0].Assignees, 1)
+ assert.Empty(t, fixtureIssues[1].Assignees)
+ assert.Len(t, fixtureIssues[0].Attachments, 1)
+ assert.Len(t, fixtureIssues[1].Attachments, 1)
+ assert.Len(t, fixtureIssues[2].Attachments, 2)
+ assert.Len(t, fixtureIssues[0].Comments, 3)
+ assert.Len(t, fixtureIssues[1].Comments, 4)
+ assert.Len(t, fixtureIssues[2].Comments, 1)
+ for _, issue := range fixtureIssues {
+ assert.Equal(t, issue.RepoID, issue.Repo.ID)
+ for _, label := range issue.Labels {
+ assert.Equal(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: label.ID}), label)
+ unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID})
+ }
+ if issue.PosterID > 0 {
+ assert.Equal(t, issue.PosterID, issue.Poster.ID)
+ }
+ if issue.AssigneeID > 0 {
+ assert.Equal(t, issue.AssigneeID, issue.Assignee.ID)
+ }
+ if issue.MilestoneID > 0 {
+ assert.Equal(t, issue.MilestoneID, issue.Milestone.ID)
+ }
+ if issue.IsPull {
+ require.NotNil(t, issue.PullRequest)
+ assert.Equal(t, issue.ID, issue.PullRequest.IssueID)
+ assert.Same(t, issue, issue.PullRequest.Issue)
+ }
+ for _, attachment := range issue.Attachments {
+ assert.Equal(t, issue.ID, attachment.IssueID)
+ }
+ for _, comment := range issue.Comments {
+ assert.Equal(t, issue.ID, comment.IssueID)
+ }
+ if issue.ID == int64(1) {
+ assert.Equal(t, int64(400), issue.TotalTrackedTime)
+ }
+ if issue.ID == 1 || issue.ID == 2 {
+ require.Len(t, issue.Projects, 1)
+ assert.Equal(t, int64(1), issue.Projects[0].ID)
+ } else {
+ assert.Empty(t, issue.Projects)
+ }
+ }
+ assert.Equal(t, int64(3682), fixtureIssues[1].TotalTrackedTime)
+
+ require.NoError(t, issueList.LoadDiscussComments(t.Context()))
+ assert.Len(t, fixtureIssues[0].Comments, 2)
+ assert.Empty(t, fixtureIssues[1].Comments)
+ assert.Len(t, fixtureIssues[2].Comments, 1)
+ for _, issue := range fixtureIssues {
+ for _, comment := range issue.Comments {
+ assert.Equal(t, issue.ID, comment.IssueID)
+ assert.Equal(t, issues_model.CommentTypeComment, comment.Type)
+ }
+ }
+ })
}
diff --git a/models/issues/issue_project_multi_test.go b/models/issues/issue_project_multi_test.go
index 8023ee67d63..5b951db7aab 100644
--- a/models/issues/issue_project_multi_test.go
+++ b/models/issues/issue_project_multi_test.go
@@ -44,19 +44,14 @@ func TestIssueMultipleProjects(t *testing.T) {
require.Empty(t, issue1.Projects)
// assign issue to both projects (each project uses its own default column)
- _, _, err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue1, user2, []int64{project1.ID})
- require.NoError(t, err)
- assert.Nilf(t, issue1.Projects, "Issue's Projects should be nil after IssueAssignOrRemoveProject to ensure it reloads fresh data")
- err = issue1.LoadProjects(t.Context())
- require.NoError(t, err)
- require.Len(t, issue1.Projects, 1)
-
- _, _, err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue1, user2, []int64{project1.ID, project2.ID})
- require.NoError(t, err)
- assert.Nilf(t, issue1.Projects, "Issue's Projects should be nil after IssueAssignOrRemoveProject to ensure it reloads fresh data")
- err = issue1.LoadProjects(t.Context())
- require.NoError(t, err)
- require.Len(t, issue1.Projects, 2)
+ for _, projectIDs := range [][]int64{{project1.ID}, {project1.ID, project2.ID}} {
+ _, _, err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue1, user2, projectIDs)
+ require.NoError(t, err)
+ assert.Nilf(t, issue1.Projects, "Issue's Projects should be nil after IssueAssignOrRemoveProject to ensure it reloads fresh data")
+ err = issue1.LoadProjects(t.Context())
+ require.NoError(t, err)
+ require.Len(t, issue1.Projects, len(projectIDs))
+ }
assert.ElementsMatch(t, []int64{project1.ID, project2.ID}, []int64{issue1.Projects[0].ID, issue1.Projects[1].ID}, "Issue should be in both projects")
// test issue's project column map
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go
index 2227ae26a96..ebf736dbfc6 100644
--- a/models/issues/issue_test.go
+++ b/models/issues/issue_test.go
@@ -53,79 +53,211 @@ func TestIssue_ReplaceLabels(t *testing.T) {
testSuccess(18, []int64{6, 8, 7}, []int64{6, 7})
}
-func Test_GetIssueIDsByRepoID(t *testing.T) {
+func TestIssueQueries(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- ids, err := issues_model.GetIssueIDsByRepoID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Len(t, ids, 5)
-}
-
-func TestIssueAPIURL(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
- err := issue.LoadAttributes(t.Context())
-
- assert.NoError(t, err)
- assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL(t.Context()))
-}
-
-func TestGetIssuesByIDs(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- testSuccess := func(expectedIssueIDs, nonExistentIssueIDs []int64) {
- issues, err := issues_model.GetIssuesByIDs(t.Context(), append(expectedIssueIDs, nonExistentIssueIDs...), true)
+ t.Run("GetIssueIDsByRepoID", func(t *testing.T) {
+ ids, err := issues_model.GetIssueIDsByRepoID(t.Context(), 1)
assert.NoError(t, err)
- actualIssueIDs := make([]int64, len(issues))
- for i, issue := range issues {
- actualIssueIDs[i] = issue.ID
- }
- assert.Equal(t, expectedIssueIDs, actualIssueIDs)
- }
- testSuccess([]int64{1, 2, 3}, []int64{})
- testSuccess([]int64{1, 2, 3}, []int64{unittest.NonexistentID})
- testSuccess([]int64{3, 2, 1}, []int64{})
-}
+ assert.Len(t, ids, 5)
+ })
-func TestGetParticipantIDsByIssue(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("IssueAPIURL", func(t *testing.T) {
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
+ err := issue.LoadAttributes(t.Context())
- checkParticipants := func(issueID int64, userIDs []int) {
- issue, err := issues_model.GetIssueByID(t.Context(), issueID)
assert.NoError(t, err)
- participants, err := issue.GetParticipantIDsByIssue(t.Context())
- if assert.NoError(t, err) {
- participantsIDs := make([]int, len(participants))
- for i, uid := range participants {
- participantsIDs[i] = int(uid)
+ assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL(t.Context()))
+ })
+
+ t.Run("GetIssuesByIDs", func(t *testing.T) {
+ testSuccess := func(expectedIssueIDs, nonExistentIssueIDs []int64) {
+ issues, err := issues_model.GetIssuesByIDs(t.Context(), append(expectedIssueIDs, nonExistentIssueIDs...), true)
+ assert.NoError(t, err)
+ actualIssueIDs := make([]int64, len(issues))
+ for i, issue := range issues {
+ actualIssueIDs[i] = issue.ID
}
- sort.Ints(participantsIDs)
- sort.Ints(userIDs)
- assert.Equal(t, userIDs, participantsIDs)
+ assert.Equal(t, expectedIssueIDs, actualIssueIDs)
}
- }
+ testSuccess([]int64{1, 2, 3}, []int64{})
+ testSuccess([]int64{1, 2, 3}, []int64{unittest.NonexistentID})
+ testSuccess([]int64{3, 2, 1}, []int64{})
+ })
- // User 1 is issue1 poster (see fixtures/issue.yml)
- // User 2 only labeled issue1 (see fixtures/comment.yml)
- // Users 3 and 5 made actual comments (see fixtures/comment.yml)
- // User 3 is inactive, thus not active participant
- checkParticipants(1, []int{1, 5})
+ t.Run("GetParticipantIDsByIssue", func(t *testing.T) {
+ checkParticipants := func(issueID int64, userIDs []int) {
+ issue, err := issues_model.GetIssueByID(t.Context(), issueID)
+ assert.NoError(t, err)
+ participants, err := issue.GetParticipantIDsByIssue(t.Context())
+ if assert.NoError(t, err) {
+ participantsIDs := make([]int, len(participants))
+ for i, uid := range participants {
+ participantsIDs[i] = int(uid)
+ }
+ sort.Ints(participantsIDs)
+ sort.Ints(userIDs)
+ assert.Equal(t, userIDs, participantsIDs)
+ }
+ }
+
+ // User 1 is issue1 poster (see fixtures/issue.yml)
+ // User 2 only labeled issue1 (see fixtures/comment.yml)
+ // Users 3 and 5 made actual comments (see fixtures/comment.yml)
+ // User 3 is inactive, thus not active participant
+ checkParticipants(1, []int{1, 5})
+ })
+
+ t.Run("Issues", func(t *testing.T) {
+ publicOnlyUser2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ publicOnlyUser2.ExtDoerData = user_model.NewTokenExtDoerData(user_model.CredentialAccessToken, 1, "public-only,read:issue")
+ for _, test := range []struct {
+ Opts issues_model.IssuesOptions
+ ExpectedIssueIDs []int64
+ }{
+ {
+ issues_model.IssuesOptions{
+ AssigneeID: "1",
+ SortType: "oldest",
+ },
+ []int64{1, 6},
+ },
+ {
+ issues_model.IssuesOptions{
+ RepoCond: builder.In("repo_id", 1, 3),
+ SortType: "oldest",
+ Paginator: &db.ListOptions{
+ Page: 1,
+ PageSize: 4,
+ },
+ },
+ []int64{1, 2, 3, 5},
+ },
+ {
+ issues_model.IssuesOptions{
+ LabelIDs: []int64{1},
+ Paginator: &db.ListOptions{
+ Page: 1,
+ PageSize: 4,
+ },
+ },
+ []int64{2, 1},
+ },
+ {
+ issues_model.IssuesOptions{
+ LabelIDs: []int64{1, 2},
+ Paginator: &db.ListOptions{
+ Page: 1,
+ PageSize: 4,
+ },
+ },
+ []int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests
+ },
+ {
+ issues_model.IssuesOptions{
+ MilestoneIDs: []int64{1},
+ },
+ []int64{2},
+ },
+ {
+ issues_model.IssuesOptions{
+ SubscriberID: 11,
+ },
+ []int64{11, 5, 9, 8, 3, 2, 1},
+ },
+ {
+ issues_model.IssuesOptions{
+ RepoCond: builder.In("repo_id", 1, 2),
+ Doer: publicOnlyUser2,
+ IsPull: optional.Some(false),
+ SortType: "oldest",
+ },
+ []int64{1, 5},
+ },
+ } {
+ issues, err := issues_model.Issues(t.Context(), &test.Opts)
+ assert.NoError(t, err)
+ if assert.Len(t, issues, len(test.ExpectedIssueIDs)) {
+ for i, issue := range issues {
+ assert.Equal(t, test.ExpectedIssueIDs[i], issue.ID)
+ }
+ }
+ }
+ })
+
+ t.Run("Issue_loadTotalTimes", func(t *testing.T) {
+ ms, err := issues_model.GetIssueByID(t.Context(), 2)
+ assert.NoError(t, err)
+ assert.NoError(t, ms.LoadTotalTimes(t.Context()))
+ assert.Equal(t, int64(3682), ms.TotalTrackedTime)
+ })
+
+ t.Run("Issue_ResolveMentions", func(t *testing.T) {
+ testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) {
+ o := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: owner})
+ r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: o.ID, LowerName: repo})
+ issue := &issues_model.Issue{RepoID: r.ID}
+ d := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: doer})
+ resolved, err := issues_model.ResolveIssueMentionsByVisibility(t.Context(), issue, d, mentions)
+ assert.NoError(t, err)
+ ids := make([]int64, len(resolved))
+ for i, user := range resolved {
+ ids[i] = user.ID
+ }
+ slices.Sort(ids)
+ assert.Equal(t, expected, ids)
+ }
+
+ // Public repo, existing user
+ testSuccess("user2", "repo1", "user1", []string{"user5"}, []int64{5})
+ // Public repo, non-existing user
+ testSuccess("user2", "repo1", "user1", []string{"nonexisting"}, []int64{})
+ // Public repo, doer
+ testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{})
+ // Private repo, team member
+ testSuccess("org17", "big_test_private_4", "user20", []string{"user2"}, []int64{2})
+ // Private repo, not a team member
+ testSuccess("org17", "big_test_private_4", "user20", []string{"user5"}, []int64{})
+ // Private repo, whole team
+ testSuccess("org17", "big_test_private_4", "user15", []string{"org17/owners"}, []int64{18})
+ })
+
+ t.Run("MilestoneList_LoadTotalTrackedTimes", func(t *testing.T) {
+ miles := issues_model.MilestoneList{
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}),
+ }
+
+ assert.NoError(t, miles.LoadTotalTrackedTimes(t.Context()))
+
+ assert.Equal(t, int64(3682), miles[0].TotalTrackedTime)
+ })
+
+ t.Run("LoadTotalTrackedTime", func(t *testing.T) {
+ milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
+
+ assert.NoError(t, milestone.LoadTotalTrackedTime(t.Context()))
+
+ assert.Equal(t, int64(3682), milestone.TotalTrackedTime)
+ })
+
+ t.Run("CountIssues", func(t *testing.T) {
+ count, err := issues_model.CountIssues(t.Context(), &issues_model.IssuesOptions{})
+ assert.NoError(t, err)
+ assert.EqualValues(t, 22, count)
+ })
}
func TestIssue_ClearLabels(t *testing.T) {
- tests := []struct {
- issueID int64
- doerID int64
- }{
- {1, 2}, // non-pull-request, has labels
- {2, 2}, // pull-request, has labels
- {3, 2}, // pull-request, has no labels
- }
- for _, test := range tests {
+ for _, issueID := range []int64{
+ 1, // non-pull-request, has labels
+ 2, // pull-request, has labels
+ 3, // pull-request, has no labels
+ } {
assert.NoError(t, unittest.PrepareTestDatabase())
- issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: test.issueID})
- doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.doerID})
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueID})
+ doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
assert.NoError(t, issues_model.ClearIssueLabels(t.Context(), issue, doer))
- unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: test.issueID})
+ unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: issueID})
}
}
@@ -149,92 +281,6 @@ func TestUpdateIssueCols(t *testing.T) {
unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
}
-func TestIssues(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- publicOnlyUser2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- publicOnlyUser2.ExtDoerData = user_model.NewTokenExtDoerData(user_model.CredentialAccessToken, 1, "public-only,read:issue")
- for _, test := range []struct {
- Opts issues_model.IssuesOptions
- ExpectedIssueIDs []int64
- }{
- {
- issues_model.IssuesOptions{
- AssigneeID: "1",
- SortType: "oldest",
- },
- []int64{1, 6},
- },
- {
- issues_model.IssuesOptions{
- RepoCond: builder.In("repo_id", 1, 3),
- SortType: "oldest",
- Paginator: &db.ListOptions{
- Page: 1,
- PageSize: 4,
- },
- },
- []int64{1, 2, 3, 5},
- },
- {
- issues_model.IssuesOptions{
- LabelIDs: []int64{1},
- Paginator: &db.ListOptions{
- Page: 1,
- PageSize: 4,
- },
- },
- []int64{2, 1},
- },
- {
- issues_model.IssuesOptions{
- LabelIDs: []int64{1, 2},
- Paginator: &db.ListOptions{
- Page: 1,
- PageSize: 4,
- },
- },
- []int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests
- },
- {
- issues_model.IssuesOptions{
- MilestoneIDs: []int64{1},
- },
- []int64{2},
- },
- {
- issues_model.IssuesOptions{
- SubscriberID: 11,
- },
- []int64{11, 5, 9, 8, 3, 2, 1},
- },
- {
- issues_model.IssuesOptions{
- RepoCond: builder.In("repo_id", 1, 2),
- Doer: publicOnlyUser2,
- IsPull: optional.Some(false),
- SortType: "oldest",
- },
- []int64{1, 5},
- },
- } {
- issues, err := issues_model.Issues(t.Context(), &test.Opts)
- assert.NoError(t, err)
- if assert.Len(t, issues, len(test.ExpectedIssueIDs)) {
- for i, issue := range issues {
- assert.Equal(t, test.ExpectedIssueIDs[i], issue.ID)
- }
- }
- }
-}
-
-func TestIssue_loadTotalTimes(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- ms, err := issues_model.GetIssueByID(t.Context(), 2)
- assert.NoError(t, err)
- assert.NoError(t, ms.LoadTotalTimes(t.Context()))
- assert.Equal(t, int64(3682), ms.TotalTrackedTime)
-}
-
func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue {
var newIssue issues_model.Issue
t.Run(title, func(t *testing.T) {
@@ -276,38 +322,6 @@ func TestIssue_InsertIssue(t *testing.T) {
assert.NoError(t, err)
}
-func TestIssue_ResolveMentions(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) {
- o := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: owner})
- r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: o.ID, LowerName: repo})
- issue := &issues_model.Issue{RepoID: r.ID}
- d := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: doer})
- resolved, err := issues_model.ResolveIssueMentionsByVisibility(t.Context(), issue, d, mentions)
- assert.NoError(t, err)
- ids := make([]int64, len(resolved))
- for i, user := range resolved {
- ids[i] = user.ID
- }
- slices.Sort(ids)
- assert.Equal(t, expected, ids)
- }
-
- // Public repo, existing user
- testSuccess("user2", "repo1", "user1", []string{"user5"}, []int64{5})
- // Public repo, non-existing user
- testSuccess("user2", "repo1", "user1", []string{"nonexisting"}, []int64{})
- // Public repo, doer
- testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{})
- // Private repo, team member
- testSuccess("org17", "big_test_private_4", "user20", []string{"user2"}, []int64{2})
- // Private repo, not a team member
- testSuccess("org17", "big_test_private_4", "user20", []string{"user5"}, []int64{})
- // Private repo, whole team
- testSuccess("org17", "big_test_private_4", "user15", []string{"org17/owners"}, []int64{18})
-}
-
func TestResourceIndex(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
var wg sync.WaitGroup
@@ -365,33 +379,6 @@ func TestCorrectIssueStats(t *testing.T) {
assert.EqualValues(t, issueStats.OpenCount, issueAmount)
}
-func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- miles := issues_model.MilestoneList{
- unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}),
- }
-
- assert.NoError(t, miles.LoadTotalTrackedTimes(t.Context()))
-
- assert.Equal(t, int64(3682), miles[0].TotalTrackedTime)
-}
-
-func TestLoadTotalTrackedTime(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
-
- assert.NoError(t, milestone.LoadTotalTrackedTime(t.Context()))
-
- assert.Equal(t, int64(3682), milestone.TotalTrackedTime)
-}
-
-func TestCountIssues(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- count, err := issues_model.CountIssues(t.Context(), &issues_model.IssuesOptions{})
- assert.NoError(t, err)
- assert.EqualValues(t, 22, count)
-}
-
func TestIssueLoadAttributes(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
setting.Service.EnableTimetracking = true
diff --git a/models/issues/issue_user_test.go b/models/issues/issue_user_test.go
index 58d2698e768..b840a02c7c7 100644
--- a/models/issues/issue_user_test.go
+++ b/models/issues/issue_user_test.go
@@ -15,47 +15,47 @@ import (
"github.com/stretchr/testify/require"
)
-func Test_NewIssueUsers(t *testing.T) {
+func TestIssueUsers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- newIssue := &issues_model.Issue{
- RepoID: repo.ID,
- PosterID: 4,
- Index: 6,
- Title: "newTestIssueTitle",
- Content: "newTestIssueContent",
- }
+ t.Run("NewIssueUsers", func(t *testing.T) {
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ newIssue := &issues_model.Issue{
+ RepoID: repo.ID,
+ PosterID: 4,
+ Index: 6,
+ Title: "newTestIssueTitle",
+ Content: "newTestIssueContent",
+ }
- // artificially insert new issue
- require.NoError(t, db.Insert(t.Context(), newIssue))
- require.NoError(t, issues_model.NewIssueUsers(t.Context(), repo, newIssue))
+ // artificially insert new issue
+ require.NoError(t, db.Insert(t.Context(), newIssue))
+ require.NoError(t, issues_model.NewIssueUsers(t.Context(), repo, newIssue))
- // issue_user table should now have entries for new issue
- unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID})
- unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID})
-}
-
-func TestUpdateIssueUserByRead(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
-
- assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), 4, issue.ID))
- unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
-
- assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), 4, issue.ID))
- unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
-
- assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), unittest.NonexistentID, unittest.NonexistentID))
-}
-
-func TestUpdateIssueUsersByMentions(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
-
- uids := []int64{2, 5}
- assert.NoError(t, issues_model.UpdateIssueUsersByMentions(t.Context(), issue.ID, uids))
- for _, uid := range uids {
- unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1")
- }
+ // issue_user table should now have entries for new issue
+ unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID})
+ })
+
+ t.Run("UpdateIssueUserByRead", func(t *testing.T) {
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
+
+ assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), 4, issue.ID))
+ unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
+
+ assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), 4, issue.ID))
+ unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
+
+ assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), unittest.NonexistentID, unittest.NonexistentID))
+ })
+
+ t.Run("UpdateIssueUsersByMentions", func(t *testing.T) {
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
+
+ uids := []int64{2, 5}
+ assert.NoError(t, issues_model.UpdateIssueUsersByMentions(t.Context(), issue.ID, uids))
+ for _, uid := range uids {
+ unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1")
+ }
+ })
}
diff --git a/models/issues/issue_watch_test.go b/models/issues/issue_watch_test.go
index 9ff5bd7e8e4..5029f414c5e 100644
--- a/models/issues/issue_watch_test.go
+++ b/models/issues/issue_watch_test.go
@@ -13,55 +13,48 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestCreateOrUpdateIssueWatch(t *testing.T) {
+func TestIssueWatch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(t.Context(), 3, 1, true))
- iw := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 3, IssueID: 1})
- assert.True(t, iw.IsWatching)
+ t.Run("GetIssueWatch", func(t *testing.T) {
+ _, exists, err := issues_model.GetIssueWatch(t.Context(), 9, 1)
+ assert.True(t, exists)
+ assert.NoError(t, err)
- assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(t.Context(), 1, 1, false))
- iw = unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 1, IssueID: 1})
- assert.False(t, iw.IsWatching)
-}
-
-func TestGetIssueWatch(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- _, exists, err := issues_model.GetIssueWatch(t.Context(), 9, 1)
- assert.True(t, exists)
- assert.NoError(t, err)
-
- iw, exists, err := issues_model.GetIssueWatch(t.Context(), 2, 2)
- assert.True(t, exists)
- assert.NoError(t, err)
- assert.False(t, iw.IsWatching)
-
- _, exists, err = issues_model.GetIssueWatch(t.Context(), 3, 1)
- assert.False(t, exists)
- assert.NoError(t, err)
-}
-
-func TestGetIssueWatchers(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- iws, err := issues_model.GetIssueWatchers(t.Context(), 1, db.ListOptions{})
- assert.NoError(t, err)
- // Watcher is inactive, thus 0
- assert.Empty(t, iws)
-
- iws, err = issues_model.GetIssueWatchers(t.Context(), 2, db.ListOptions{})
- assert.NoError(t, err)
- // Watcher is explicit not watching
- assert.Empty(t, iws)
-
- iws, err = issues_model.GetIssueWatchers(t.Context(), 5, db.ListOptions{})
- assert.NoError(t, err)
- // Issue has no Watchers
- assert.Empty(t, iws)
-
- iws, err = issues_model.GetIssueWatchers(t.Context(), 7, db.ListOptions{})
- assert.NoError(t, err)
- // Issue has one watcher
- assert.Len(t, iws, 1)
+ iw, exists, err := issues_model.GetIssueWatch(t.Context(), 2, 2)
+ assert.True(t, exists)
+ assert.NoError(t, err)
+ assert.False(t, iw.IsWatching)
+
+ _, exists, err = issues_model.GetIssueWatch(t.Context(), 3, 1)
+ assert.False(t, exists)
+ assert.NoError(t, err)
+ })
+
+ t.Run("GetIssueWatchers", func(t *testing.T) {
+ for _, issueID := range []int64{
+ 1, // Watcher is inactive, thus 0
+ 2, // Watcher is explicit not watching
+ 5, // Issue has no Watchers
+ } {
+ iws, err := issues_model.GetIssueWatchers(t.Context(), issueID, db.ListOptions{})
+ assert.NoError(t, err)
+ assert.Empty(t, iws)
+ }
+
+ iws, err := issues_model.GetIssueWatchers(t.Context(), 7, db.ListOptions{})
+ assert.NoError(t, err)
+ // Issue has one watcher
+ assert.Len(t, iws, 1)
+ })
+
+ t.Run("CreateOrUpdateIssueWatch", func(t *testing.T) {
+ assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(t.Context(), 3, 1, true))
+ iw := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 3, IssueID: 1})
+ assert.True(t, iw.IsWatching)
+
+ assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(t.Context(), 1, 1, false))
+ iw = unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 1, IssueID: 1})
+ assert.False(t, iw.IsWatching)
+ })
}
diff --git a/models/issues/label_test.go b/models/issues/label_test.go
index 547c1dd978c..e22693ec674 100644
--- a/models/issues/label_test.go
+++ b/models/issues/label_test.go
@@ -17,41 +17,190 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestLabel_CalOpenIssues(t *testing.T) {
+func TestLabelQueries(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
- label.CalOpenIssues()
- assert.Equal(t, 2, label.NumOpenIssues)
-}
+ t.Run("Label_CalOpenIssues", func(t *testing.T) {
+ label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
+ label.CalOpenIssues()
+ assert.Equal(t, 2, label.NumOpenIssues)
+ })
+ t.Run("Label_LoadSelectedLabelsAfterClick", func(t *testing.T) {
+ // Loading the label id:8 which have a scope and an exclusivity
+ label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 8})
-func TestLabel_LoadSelectedLabelsAfterClick(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- // Loading the label id:8 which have a scope and an exclusivity
- label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 8})
+ // First test : with negative and scope
+ label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"})
+ assert.Equal(t, "1", label.QueryString)
+ assert.True(t, label.IsSelected)
- // First test : with negative and scope
- label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"})
- assert.Equal(t, "1", label.QueryString)
- assert.True(t, label.IsSelected)
+ // Second test : with duplicates
+ label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"})
+ assert.Equal(t, "1,8", label.QueryString)
+ assert.False(t, label.IsSelected)
- // Second test : with duplicates
- label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"})
- assert.Equal(t, "1,8", label.QueryString)
- assert.False(t, label.IsSelected)
+ // Third test : empty set
+ label.LoadSelectedLabelsAfterClick([]int64{}, []string{})
+ assert.False(t, label.IsSelected)
+ assert.Equal(t, "8", label.QueryString)
+ })
+ t.Run("Label_ExclusiveScope", func(t *testing.T) {
+ label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 7})
+ assert.Equal(t, "scope", label.ExclusiveScope())
- // Third test : empty set
- label.LoadSelectedLabelsAfterClick([]int64{}, []string{})
- assert.False(t, label.IsSelected)
- assert.Equal(t, "8", label.QueryString)
-}
+ label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 9})
+ assert.Equal(t, "scope/subscope", label.ExclusiveScope())
+ })
+ t.Run("GetLabelByID", func(t *testing.T) {
+ label, err := issues_model.GetLabelByID(t.Context(), 1)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, label.ID)
-func TestLabel_ExclusiveScope(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 7})
- assert.Equal(t, "scope", label.ExclusiveScope())
+ _, err = issues_model.GetLabelByID(t.Context(), unittest.NonexistentID)
+ assert.True(t, issues_model.IsErrLabelNotExist(err))
+ })
+ t.Run("GetLabelInRepoByName", func(t *testing.T) {
+ label, err := issues_model.GetLabelInRepoByName(t.Context(), 1, "label1")
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, label.ID)
+ assert.Equal(t, "label1", label.Name)
- label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 9})
- assert.Equal(t, "scope/subscope", label.ExclusiveScope())
+ _, err = issues_model.GetLabelInRepoByName(t.Context(), 1, "")
+ assert.ErrorIs(t, err, util.ErrNotExist)
+
+ _, err = issues_model.GetLabelInRepoByName(t.Context(), unittest.NonexistentID, "nonexistent")
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ })
+ t.Run("GetLabelInRepoByNames", func(t *testing.T) {
+ for _, names := range [][]string{
+ {"label1", "label2"},
+ // label3 doesn't exists.. See labels.yml
+ {"label1", "label2", "label3"},
+ } {
+ labelIDs, err := issues_model.GetLabelIDsInRepoByNames(t.Context(), 1, names)
+ assert.NoError(t, err)
+ assert.Len(t, labelIDs, 2)
+ assert.Equal(t, int64(1), labelIDs[0])
+ assert.Equal(t, int64(2), labelIDs[1])
+ }
+ })
+ t.Run("GetLabelInRepoByID", func(t *testing.T) {
+ label, err := issues_model.GetLabelInRepoByID(t.Context(), 1, 1)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, label.ID)
+
+ _, err = issues_model.GetLabelInRepoByID(t.Context(), 1, -1)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+
+ _, err = issues_model.GetLabelInRepoByID(t.Context(), unittest.NonexistentID, unittest.NonexistentID)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ })
+ t.Run("GetLabelsInRepoByIDs", func(t *testing.T) {
+ labels, err := issues_model.GetLabelsInRepoByIDs(t.Context(), 1, []int64{1, 2, unittest.NonexistentID})
+ assert.NoError(t, err)
+ if assert.Len(t, labels, 2) {
+ assert.EqualValues(t, 1, labels[0].ID)
+ assert.EqualValues(t, 2, labels[1].ID)
+ }
+ })
+
+ // Org versions
+ t.Run("GetLabelInOrgByName", func(t *testing.T) {
+ label, err := issues_model.GetLabelInOrgByName(t.Context(), 3, "orglabel3")
+ assert.NoError(t, err)
+ assert.EqualValues(t, 3, label.ID)
+ assert.Equal(t, "orglabel3", label.Name)
+
+ _, err = issues_model.GetLabelInOrgByName(t.Context(), 3, "")
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+
+ _, err = issues_model.GetLabelInOrgByName(t.Context(), 0, "orglabel3")
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+
+ _, err = issues_model.GetLabelInOrgByName(t.Context(), -1, "orglabel3")
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+
+ _, err = issues_model.GetLabelInOrgByName(t.Context(), unittest.NonexistentID, "nonexistent")
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+ })
+ t.Run("GetLabelInOrgByID", func(t *testing.T) {
+ label, err := issues_model.GetLabelInOrgByID(t.Context(), 3, 3)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 3, label.ID)
+
+ _, err = issues_model.GetLabelInOrgByID(t.Context(), 3, -1)
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+
+ _, err = issues_model.GetLabelInOrgByID(t.Context(), 0, 3)
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+
+ _, err = issues_model.GetLabelInOrgByID(t.Context(), -1, 3)
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+
+ _, err = issues_model.GetLabelInOrgByID(t.Context(), unittest.NonexistentID, unittest.NonexistentID)
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+ })
+ t.Run("GetLabelsInOrgByIDs", func(t *testing.T) {
+ labels, err := issues_model.GetLabelsInOrgByIDs(t.Context(), 3, []int64{3, 4, unittest.NonexistentID})
+ assert.NoError(t, err)
+ if assert.Len(t, labels, 2) {
+ assert.EqualValues(t, 3, labels[0].ID)
+ assert.EqualValues(t, 4, labels[1].ID)
+ }
+ })
+ t.Run("GetLabelsByIssueID", func(t *testing.T) {
+ labels, err := issues_model.GetLabelsByIssueID(t.Context(), 1)
+ assert.NoError(t, err)
+ if assert.Len(t, labels, 1) {
+ assert.EqualValues(t, 1, labels[0].ID)
+ }
+
+ labels, err = issues_model.GetLabelsByIssueID(t.Context(), unittest.NonexistentID)
+ assert.NoError(t, err)
+ assert.Empty(t, labels)
+ })
+ t.Run("HasIssueLabel", func(t *testing.T) {
+ assert.True(t, issues_model.HasIssueLabel(t.Context(), 1, 1))
+ assert.False(t, issues_model.HasIssueLabel(t.Context(), 1, 2))
+ assert.False(t, issues_model.HasIssueLabel(t.Context(), unittest.NonexistentID, unittest.NonexistentID))
+ })
+ t.Run("GetLabelsByRepoID", func(t *testing.T) {
+ _, err := db.GetEngine(t.Context()).ID(2).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3})
+ assert.NoError(t, err)
+ testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) {
+ labels, err := issues_model.GetLabelsByRepoID(t.Context(), repoID, sortType, db.ListOptions{})
+ assert.NoError(t, err)
+ assert.Len(t, labels, len(expectedIssueIDs))
+ for i, label := range labels {
+ assert.Equal(t, expectedIssueIDs[i], label.ID)
+ }
+ }
+ testSuccess(1, "leastissues", []int64{2, 1})
+ testSuccess(1, "mostissues", []int64{1, 2})
+ testSuccess(1, "reversealphabetically", []int64{2, 1})
+ testSuccess(1, "default", []int64{1, 2})
+ })
+ t.Run("GetLabelsByOrgID", func(t *testing.T) {
+ _, err := db.GetEngine(t.Context()).ID(3).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3})
+ assert.NoError(t, err)
+ testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) {
+ labels, err := issues_model.GetLabelsByOrgID(t.Context(), orgID, sortType, db.ListOptions{})
+ assert.NoError(t, err)
+ assert.Len(t, labels, len(expectedIssueIDs))
+ for i, label := range labels {
+ assert.Equal(t, expectedIssueIDs[i], label.ID)
+ }
+ }
+ testSuccess(3, "leastissues", []int64{3, 4})
+ testSuccess(3, "mostissues", []int64{4, 3})
+ testSuccess(3, "reversealphabetically", []int64{4, 3})
+ testSuccess(3, "default", []int64{3, 4})
+
+ _, err = issues_model.GetLabelsByOrgID(t.Context(), 0, "leastissues", db.ListOptions{})
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+
+ _, err = issues_model.GetLabelsByOrgID(t.Context(), -1, "leastissues", db.ListOptions{})
+ assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
+ })
}
func TestSortLabelsForDisplay(t *testing.T) {
@@ -118,185 +267,6 @@ func TestNewLabels(t *testing.T) {
unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{})
}
-func TestGetLabelByID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- label, err := issues_model.GetLabelByID(t.Context(), 1)
- assert.NoError(t, err)
- assert.EqualValues(t, 1, label.ID)
-
- _, err = issues_model.GetLabelByID(t.Context(), unittest.NonexistentID)
- assert.True(t, issues_model.IsErrLabelNotExist(err))
-}
-
-func TestGetLabelInRepoByName(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- label, err := issues_model.GetLabelInRepoByName(t.Context(), 1, "label1")
- assert.NoError(t, err)
- assert.EqualValues(t, 1, label.ID)
- assert.Equal(t, "label1", label.Name)
-
- _, err = issues_model.GetLabelInRepoByName(t.Context(), 1, "")
- assert.ErrorIs(t, err, util.ErrNotExist)
-
- _, err = issues_model.GetLabelInRepoByName(t.Context(), unittest.NonexistentID, "nonexistent")
- assert.ErrorIs(t, err, util.ErrNotExist)
-}
-
-func TestGetLabelInRepoByNames(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- labelIDs, err := issues_model.GetLabelIDsInRepoByNames(t.Context(), 1, []string{"label1", "label2"})
- assert.NoError(t, err)
-
- assert.Len(t, labelIDs, 2)
-
- assert.Equal(t, int64(1), labelIDs[0])
- assert.Equal(t, int64(2), labelIDs[1])
-}
-
-func TestGetLabelInRepoByNamesDiscardsNonExistentLabels(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- // label3 doesn't exists.. See labels.yml
- labelIDs, err := issues_model.GetLabelIDsInRepoByNames(t.Context(), 1, []string{"label1", "label2", "label3"})
- assert.NoError(t, err)
-
- assert.Len(t, labelIDs, 2)
-
- assert.Equal(t, int64(1), labelIDs[0])
- assert.Equal(t, int64(2), labelIDs[1])
- assert.NoError(t, err)
-}
-
-func TestGetLabelInRepoByID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- label, err := issues_model.GetLabelInRepoByID(t.Context(), 1, 1)
- assert.NoError(t, err)
- assert.EqualValues(t, 1, label.ID)
-
- _, err = issues_model.GetLabelInRepoByID(t.Context(), 1, -1)
- assert.ErrorIs(t, err, util.ErrNotExist)
-
- _, err = issues_model.GetLabelInRepoByID(t.Context(), unittest.NonexistentID, unittest.NonexistentID)
- assert.ErrorIs(t, err, util.ErrNotExist)
-}
-
-func TestGetLabelsInRepoByIDs(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- labels, err := issues_model.GetLabelsInRepoByIDs(t.Context(), 1, []int64{1, 2, unittest.NonexistentID})
- assert.NoError(t, err)
- if assert.Len(t, labels, 2) {
- assert.EqualValues(t, 1, labels[0].ID)
- assert.EqualValues(t, 2, labels[1].ID)
- }
-}
-
-func TestGetLabelsByRepoID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- _, err := db.GetEngine(t.Context()).ID(2).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3})
- assert.NoError(t, err)
- testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) {
- labels, err := issues_model.GetLabelsByRepoID(t.Context(), repoID, sortType, db.ListOptions{})
- assert.NoError(t, err)
- assert.Len(t, labels, len(expectedIssueIDs))
- for i, label := range labels {
- assert.Equal(t, expectedIssueIDs[i], label.ID)
- }
- }
- testSuccess(1, "leastissues", []int64{2, 1})
- testSuccess(1, "mostissues", []int64{1, 2})
- testSuccess(1, "reversealphabetically", []int64{2, 1})
- testSuccess(1, "default", []int64{1, 2})
-}
-
-// Org versions
-
-func TestGetLabelInOrgByName(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- label, err := issues_model.GetLabelInOrgByName(t.Context(), 3, "orglabel3")
- assert.NoError(t, err)
- assert.EqualValues(t, 3, label.ID)
- assert.Equal(t, "orglabel3", label.Name)
-
- _, err = issues_model.GetLabelInOrgByName(t.Context(), 3, "")
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-
- _, err = issues_model.GetLabelInOrgByName(t.Context(), 0, "orglabel3")
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-
- _, err = issues_model.GetLabelInOrgByName(t.Context(), -1, "orglabel3")
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-
- _, err = issues_model.GetLabelInOrgByName(t.Context(), unittest.NonexistentID, "nonexistent")
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-}
-
-func TestGetLabelInOrgByID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- label, err := issues_model.GetLabelInOrgByID(t.Context(), 3, 3)
- assert.NoError(t, err)
- assert.EqualValues(t, 3, label.ID)
-
- _, err = issues_model.GetLabelInOrgByID(t.Context(), 3, -1)
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-
- _, err = issues_model.GetLabelInOrgByID(t.Context(), 0, 3)
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-
- _, err = issues_model.GetLabelInOrgByID(t.Context(), -1, 3)
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-
- _, err = issues_model.GetLabelInOrgByID(t.Context(), unittest.NonexistentID, unittest.NonexistentID)
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-}
-
-func TestGetLabelsInOrgByIDs(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- labels, err := issues_model.GetLabelsInOrgByIDs(t.Context(), 3, []int64{3, 4, unittest.NonexistentID})
- assert.NoError(t, err)
- if assert.Len(t, labels, 2) {
- assert.EqualValues(t, 3, labels[0].ID)
- assert.EqualValues(t, 4, labels[1].ID)
- }
-}
-
-func TestGetLabelsByOrgID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- _, err := db.GetEngine(t.Context()).ID(3).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3})
- assert.NoError(t, err)
- testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) {
- labels, err := issues_model.GetLabelsByOrgID(t.Context(), orgID, sortType, db.ListOptions{})
- assert.NoError(t, err)
- assert.Len(t, labels, len(expectedIssueIDs))
- for i, label := range labels {
- assert.Equal(t, expectedIssueIDs[i], label.ID)
- }
- }
- testSuccess(3, "leastissues", []int64{3, 4})
- testSuccess(3, "mostissues", []int64{4, 3})
- testSuccess(3, "reversealphabetically", []int64{4, 3})
- testSuccess(3, "default", []int64{3, 4})
-
- _, err = issues_model.GetLabelsByOrgID(t.Context(), 0, "leastissues", db.ListOptions{})
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-
- _, err = issues_model.GetLabelsByOrgID(t.Context(), -1, "leastissues", db.ListOptions{})
- assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
-}
-
-//
-
-func TestGetLabelsByIssueID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- labels, err := issues_model.GetLabelsByIssueID(t.Context(), 1)
- assert.NoError(t, err)
- if assert.Len(t, labels, 1) {
- assert.EqualValues(t, 1, labels[0].ID)
- }
-
- labels, err = issues_model.GetLabelsByIssueID(t.Context(), unittest.NonexistentID)
- assert.NoError(t, err)
- assert.Empty(t, labels)
-}
-
func TestUpdateLabel(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
@@ -350,13 +320,6 @@ func TestDeleteLabel(t *testing.T) {
unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{})
}
-func TestHasIssueLabel(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- assert.True(t, issues_model.HasIssueLabel(t.Context(), 1, 1))
- assert.False(t, issues_model.HasIssueLabel(t.Context(), 1, 2))
- assert.False(t, issues_model.HasIssueLabel(t.Context(), unittest.NonexistentID, unittest.NonexistentID))
-}
-
func TestNewIssueLabel(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2})
diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go
index 925038f68e2..262e59b1451 100644
--- a/models/issues/milestone_test.go
+++ b/models/issues/milestone_test.go
@@ -24,263 +24,189 @@ func TestMilestone_State(t *testing.T) {
assert.Equal(t, api.StateClosed, (&issues_model.Milestone{IsClosed: true}).State())
}
-func TestGetMilestoneByRepoID(t *testing.T) {
+func TestMilestoneQueries(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
-
- milestone, err := issues_model.GetMilestoneByRepoID(t.Context(), 1, 1)
- assert.NoError(t, err)
- assert.EqualValues(t, 1, milestone.ID)
- assert.EqualValues(t, 1, milestone.RepoID)
-
- _, err = issues_model.GetMilestoneByRepoID(t.Context(), unittest.NonexistentID, unittest.NonexistentID)
- assert.True(t, issues_model.IsErrMilestoneNotExist(err))
-}
-
-func TestGetMilestonesByRepoID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- test := func(repoID int64, state api.StateType) {
- var isClosed optional.Option[bool]
- switch state {
- case api.StateClosed, api.StateOpen:
- isClosed = optional.Some(state == api.StateClosed)
- }
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
- milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- RepoID: repo.ID,
- IsClosed: isClosed,
- })
+ t.Run("GetMilestoneByRepoID", func(t *testing.T) {
+ milestone, err := issues_model.GetMilestoneByRepoID(t.Context(), 1, 1)
assert.NoError(t, err)
+ assert.EqualValues(t, 1, milestone.ID)
+ assert.EqualValues(t, 1, milestone.RepoID)
- var n int
-
- switch state {
- case api.StateClosed:
- n = repo.NumClosedMilestones
-
- case api.StateAll:
- n = repo.NumMilestones
-
- case api.StateOpen:
- fallthrough
-
- default:
- n = repo.NumOpenMilestones
- }
-
- assert.Len(t, milestones, n)
- for _, milestone := range milestones {
- assert.Equal(t, repoID, milestone.RepoID)
- }
- }
- test(1, api.StateOpen)
- test(1, api.StateAll)
- test(1, api.StateClosed)
- test(2, api.StateOpen)
- test(2, api.StateAll)
- test(2, api.StateClosed)
- test(3, api.StateOpen)
- test(3, api.StateClosed)
- test(3, api.StateAll)
-
- milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- RepoID: unittest.NonexistentID,
- IsClosed: optional.Some(false),
+ _, err = issues_model.GetMilestoneByRepoID(t.Context(), unittest.NonexistentID, unittest.NonexistentID)
+ assert.True(t, issues_model.IsErrMilestoneNotExist(err))
})
- assert.NoError(t, err)
- assert.Empty(t, milestones)
-}
-
-func TestGetMilestones(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- test := func(sortType string, sortCond func(*issues_model.Milestone) int) {
- for _, page := range []int{0, 1} {
+ t.Run("GetMilestonesByRepoID", func(t *testing.T) {
+ test := func(repoID int64, state api.StateType) {
+ var isClosed optional.Option[bool]
+ switch state {
+ case api.StateClosed, api.StateOpen:
+ isClosed = optional.Some(state == api.StateClosed)
+ }
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- ListOptions: db.ListOptions{
- Page: page,
- PageSize: setting.UI.IssuePagingNum,
- },
RepoID: repo.ID,
- IsClosed: optional.Some(false),
- SortType: sortType,
+ IsClosed: isClosed,
})
assert.NoError(t, err)
- assert.Len(t, milestones, repo.NumMilestones-repo.NumClosedMilestones)
- values := make([]int, len(milestones))
- for i, milestone := range milestones {
- values[i] = sortCond(milestone)
- }
- assert.True(t, sort.IntsAreSorted(values))
- milestones, err = db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- ListOptions: db.ListOptions{
- Page: page,
- PageSize: setting.UI.IssuePagingNum,
- },
- RepoID: repo.ID,
+ var n int
+
+ switch state {
+ case api.StateClosed:
+ n = repo.NumClosedMilestones
+
+ case api.StateAll:
+ n = repo.NumMilestones
+
+ case api.StateOpen:
+ fallthrough
+
+ default:
+ n = repo.NumOpenMilestones
+ }
+
+ assert.Len(t, milestones, n)
+ for _, milestone := range milestones {
+ assert.Equal(t, repoID, milestone.RepoID)
+ }
+ }
+ test(1, api.StateOpen)
+ test(1, api.StateAll)
+ test(1, api.StateClosed)
+ test(2, api.StateOpen)
+ test(2, api.StateAll)
+ test(2, api.StateClosed)
+ test(3, api.StateOpen)
+ test(3, api.StateClosed)
+ test(3, api.StateAll)
+
+ milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
+ RepoID: unittest.NonexistentID,
+ IsClosed: optional.Some(false),
+ })
+ assert.NoError(t, err)
+ assert.Empty(t, milestones)
+ })
+ t.Run("GetMilestones", func(t *testing.T) {
+ test := func(t *testing.T, options issues_model.FindMilestoneOptions, numOpen, numClosed int) {
+ testSort := func(sortType string, sortCond func(*issues_model.Milestone) int) {
+ for _, page := range []int{0, 1} {
+ for _, isClosed := range []bool{false, true} {
+ options.ListOptions = db.ListOptions{
+ Page: page,
+ PageSize: setting.UI.IssuePagingNum,
+ }
+ options.IsClosed = optional.Some(isClosed)
+ options.SortType = sortType
+ milestones, err := db.Find[issues_model.Milestone](t.Context(), options)
+ assert.NoError(t, err)
+ expectedCount := numOpen
+ if isClosed {
+ expectedCount = numClosed
+ }
+ assert.Len(t, milestones, expectedCount)
+ values := make([]int, len(milestones))
+ for i, milestone := range milestones {
+ values[i] = sortCond(milestone)
+ }
+ assert.True(t, sort.IntsAreSorted(values))
+ }
+ }
+ }
+ testSort("furthestduedate", func(milestone *issues_model.Milestone) int {
+ return -int(milestone.DeadlineUnix)
+ })
+ testSort("leastcomplete", func(milestone *issues_model.Milestone) int {
+ return milestone.Completeness
+ })
+ testSort("mostcomplete", func(milestone *issues_model.Milestone) int {
+ return -milestone.Completeness
+ })
+ testSort("leastissues", func(milestone *issues_model.Milestone) int {
+ return milestone.NumIssues
+ })
+ testSort("mostissues", func(milestone *issues_model.Milestone) int {
+ return -milestone.NumIssues
+ })
+ testSort("soonestduedate", func(milestone *issues_model.Milestone) int {
+ return int(milestone.DeadlineUnix)
+ })
+ }
+ t.Run("repo", func(t *testing.T) {
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ test(t, issues_model.FindMilestoneOptions{RepoID: repo.ID}, repo.NumMilestones-repo.NumClosedMilestones, repo.NumClosedMilestones)
+ })
+ t.Run("repos", func(t *testing.T) {
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
+ test(t, issues_model.FindMilestoneOptions{RepoIDs: []int64{repo1.ID, repo2.ID}},
+ repo1.NumOpenMilestones+repo2.NumOpenMilestones, repo1.NumClosedMilestones+repo2.NumClosedMilestones)
+ })
+ })
+ t.Run("CountRepoMilestones", func(t *testing.T) {
+ test := func(repoID int64) {
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
+ count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
+ RepoID: repoID,
+ })
+ assert.NoError(t, err)
+ assert.EqualValues(t, repo.NumMilestones, count)
+ }
+ test(1)
+ test(2)
+ test(3)
+
+ count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
+ RepoID: unittest.NonexistentID,
+ })
+ assert.NoError(t, err)
+ assert.EqualValues(t, 0, count)
+ })
+ t.Run("CountRepoClosedMilestones", func(t *testing.T) {
+ test := func(repoID int64) {
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
+ count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
+ RepoID: repoID,
IsClosed: optional.Some(true),
- Name: "",
- SortType: sortType,
})
assert.NoError(t, err)
- assert.Len(t, milestones, repo.NumClosedMilestones)
- values = make([]int, len(milestones))
- for i, milestone := range milestones {
- values[i] = sortCond(milestone)
- }
- assert.True(t, sort.IntsAreSorted(values))
+ assert.EqualValues(t, repo.NumClosedMilestones, count)
}
- }
- test("furthestduedate", func(milestone *issues_model.Milestone) int {
- return -int(milestone.DeadlineUnix)
- })
- test("leastcomplete", func(milestone *issues_model.Milestone) int {
- return milestone.Completeness
- })
- test("mostcomplete", func(milestone *issues_model.Milestone) int {
- return -milestone.Completeness
- })
- test("leastissues", func(milestone *issues_model.Milestone) int {
- return milestone.NumIssues
- })
- test("mostissues", func(milestone *issues_model.Milestone) int {
- return -milestone.NumIssues
- })
- test("soonestduedate", func(milestone *issues_model.Milestone) int {
- return int(milestone.DeadlineUnix)
- })
-}
+ test(1)
+ test(2)
+ test(3)
-func TestCountRepoMilestones(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- test := func(repoID int64) {
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- RepoID: repoID,
- })
- assert.NoError(t, err)
- assert.EqualValues(t, repo.NumMilestones, count)
- }
- test(1)
- test(2)
- test(3)
-
- count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- RepoID: unittest.NonexistentID,
- })
- assert.NoError(t, err)
- assert.EqualValues(t, 0, count)
-}
-
-func TestCountRepoClosedMilestones(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- test := func(repoID int64) {
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
- count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- RepoID: repoID,
+ RepoID: unittest.NonexistentID,
IsClosed: optional.Some(true),
})
assert.NoError(t, err)
- assert.EqualValues(t, repo.NumClosedMilestones, count)
- }
- test(1)
- test(2)
- test(3)
-
- count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- RepoID: unittest.NonexistentID,
- IsClosed: optional.Some(true),
+ assert.EqualValues(t, 0, count)
})
- assert.NoError(t, err)
- assert.EqualValues(t, 0, count)
-}
+ t.Run("CountMilestonesByRepoIDs", func(t *testing.T) {
+ milestonesCount := func(repoID int64) (int, int) {
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
+ return repo.NumOpenMilestones, repo.NumClosedMilestones
+ }
+ repo1OpenCount, repo1ClosedCount := milestonesCount(1)
+ repo2OpenCount, repo2ClosedCount := milestonesCount(2)
-func TestCountMilestonesByRepoIDs(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- milestonesCount := func(repoID int64) (int, int) {
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
- return repo.NumOpenMilestones, repo.NumClosedMilestones
- }
- repo1OpenCount, repo1ClosedCount := milestonesCount(1)
- repo2OpenCount, repo2ClosedCount := milestonesCount(2)
-
- openCounts, err := issues_model.CountMilestonesMap(t.Context(), issues_model.FindMilestoneOptions{
- RepoIDs: []int64{1, 2},
- IsClosed: optional.Some(false),
- })
- assert.NoError(t, err)
- assert.EqualValues(t, repo1OpenCount, openCounts[1])
- assert.EqualValues(t, repo2OpenCount, openCounts[2])
-
- closedCounts, err := issues_model.CountMilestonesMap(t.Context(),
- issues_model.FindMilestoneOptions{
+ openCounts, err := issues_model.CountMilestonesMap(t.Context(), issues_model.FindMilestoneOptions{
RepoIDs: []int64{1, 2},
- IsClosed: optional.Some(true),
+ IsClosed: optional.Some(false),
})
- assert.NoError(t, err)
- assert.EqualValues(t, repo1ClosedCount, closedCounts[1])
- assert.EqualValues(t, repo2ClosedCount, closedCounts[2])
-}
+ assert.NoError(t, err)
+ assert.EqualValues(t, repo1OpenCount, openCounts[1])
+ assert.EqualValues(t, repo2OpenCount, openCounts[2])
-func TestGetMilestonesByRepoIDs(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
- test := func(sortType string, sortCond func(*issues_model.Milestone) int) {
- for _, page := range []int{0, 1} {
- openMilestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
- ListOptions: db.ListOptions{
- Page: page,
- PageSize: setting.UI.IssuePagingNum,
- },
- RepoIDs: []int64{repo1.ID, repo2.ID},
- IsClosed: optional.Some(false),
- SortType: sortType,
+ closedCounts, err := issues_model.CountMilestonesMap(t.Context(),
+ issues_model.FindMilestoneOptions{
+ RepoIDs: []int64{1, 2},
+ IsClosed: optional.Some(true),
})
- assert.NoError(t, err)
- assert.Len(t, openMilestones, repo1.NumOpenMilestones+repo2.NumOpenMilestones)
- values := make([]int, len(openMilestones))
- for i, milestone := range openMilestones {
- values[i] = sortCond(milestone)
- }
- assert.True(t, sort.IntsAreSorted(values))
-
- closedMilestones, err := db.Find[issues_model.Milestone](t.Context(),
- issues_model.FindMilestoneOptions{
- ListOptions: db.ListOptions{
- Page: page,
- PageSize: setting.UI.IssuePagingNum,
- },
- RepoIDs: []int64{repo1.ID, repo2.ID},
- IsClosed: optional.Some(true),
- SortType: sortType,
- })
- assert.NoError(t, err)
- assert.Len(t, closedMilestones, repo1.NumClosedMilestones+repo2.NumClosedMilestones)
- values = make([]int, len(closedMilestones))
- for i, milestone := range closedMilestones {
- values[i] = sortCond(milestone)
- }
- assert.True(t, sort.IntsAreSorted(values))
- }
- }
- test("furthestduedate", func(milestone *issues_model.Milestone) int {
- return -int(milestone.DeadlineUnix)
- })
- test("leastcomplete", func(milestone *issues_model.Milestone) int {
- return milestone.Completeness
- })
- test("mostcomplete", func(milestone *issues_model.Milestone) int {
- return -milestone.Completeness
- })
- test("leastissues", func(milestone *issues_model.Milestone) int {
- return milestone.NumIssues
- })
- test("mostissues", func(milestone *issues_model.Milestone) int {
- return -milestone.NumIssues
- })
- test("soonestduedate", func(milestone *issues_model.Milestone) int {
- return int(milestone.DeadlineUnix)
+ assert.NoError(t, err)
+ assert.EqualValues(t, repo1ClosedCount, closedCounts[1])
+ assert.EqualValues(t, repo2ClosedCount, closedCounts[2])
})
}
diff --git a/models/issues/review_test.go b/models/issues/review_test.go
index f52f2b9359e..29aff876df1 100644
--- a/models/issues/review_test.go
+++ b/models/issues/review_test.go
@@ -225,33 +225,14 @@ func TestDismissReview(t *testing.T) {
assert.True(t, rejectReviewExample.Dismissed)
assert.False(t, requestReviewExample.Dismissed)
- assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, true))
- rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
- requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
- assert.True(t, rejectReviewExample.Dismissed)
- assert.False(t, requestReviewExample.Dismissed)
- assert.False(t, approveReviewExample.Dismissed)
-
- assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, true))
- rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
- requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
- assert.True(t, rejectReviewExample.Dismissed)
- assert.False(t, requestReviewExample.Dismissed)
- assert.False(t, approveReviewExample.Dismissed)
-
- assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, false))
- rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
- requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
- assert.True(t, rejectReviewExample.Dismissed)
- assert.False(t, requestReviewExample.Dismissed)
- assert.False(t, approveReviewExample.Dismissed)
-
- assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, false))
- rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
- requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
- assert.True(t, rejectReviewExample.Dismissed)
- assert.False(t, requestReviewExample.Dismissed)
- assert.False(t, approveReviewExample.Dismissed)
+ for _, dismissed := range []bool{true, true, false, false} {
+ assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, dismissed))
+ rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
+ requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
+ assert.True(t, rejectReviewExample.Dismissed)
+ assert.False(t, requestReviewExample.Dismissed)
+ assert.False(t, approveReviewExample.Dismissed)
+ }
assert.NoError(t, issues_model.DismissReview(t.Context(), rejectReviewExample, false))
assert.False(t, rejectReviewExample.Dismissed)
diff --git a/models/organization/org_test.go b/models/organization/org_test.go
index 028b0c4434e..c134e8c9d68 100644
--- a/models/organization/org_test.go
+++ b/models/organization/org_test.go
@@ -435,79 +435,80 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
testSuccess(4, []int64{})
}
-func TestHasOrgVisibleTypePublic(t *testing.T) {
+func TestHasOrgVisibleType(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
- const newOrgName = "test-org-public"
- org := &organization.Organization{
- Name: newOrgName,
- Visibility: structs.VisibleTypePublic,
- }
+ t.Run("Public", func(t *testing.T) {
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
- unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
- assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner))
- org = unittest.AssertExistsAndLoadBean(t,
- &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
- test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner)
- test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3)
- test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil)
- assert.True(t, test1) // owner of org
- assert.True(t, test2) // user not a part of org
- assert.True(t, test3) // logged out user
+ const newOrgName = "test-org-public"
+ org := &organization.Organization{
+ Name: newOrgName,
+ Visibility: structs.VisibleTypePublic,
+ }
- restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29, IsRestricted: true})
- require.True(t, restrictedUser.IsRestricted)
- assert.True(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser))
- defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
- assert.False(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser))
-}
+ unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
+ assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner))
+ org = unittest.AssertExistsAndLoadBean(t,
+ &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
+ test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner)
+ test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3)
+ test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil)
+ assert.True(t, test1) // owner of org
+ assert.True(t, test2) // user not a part of org
+ assert.True(t, test3) // logged out user
-func TestHasOrgVisibleTypeLimited(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
+ restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29, IsRestricted: true})
+ require.True(t, restrictedUser.IsRestricted)
+ assert.True(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser))
+ defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
+ assert.False(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser))
+ })
- const newOrgName = "test-org-limited"
- org := &organization.Organization{
- Name: newOrgName,
- Visibility: structs.VisibleTypeLimited,
- }
+ t.Run("Limited", func(t *testing.T) {
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
- unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
- assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner))
- org = unittest.AssertExistsAndLoadBean(t,
- &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
- test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner)
- test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3)
- test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil)
- assert.True(t, test1) // owner of org
- assert.True(t, test2) // user not a part of org
- assert.False(t, test3) // logged out user
-}
+ const newOrgName = "test-org-limited"
+ org := &organization.Organization{
+ Name: newOrgName,
+ Visibility: structs.VisibleTypeLimited,
+ }
-func TestHasOrgVisibleTypePrivate(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
+ unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
+ assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner))
+ org = unittest.AssertExistsAndLoadBean(t,
+ &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
+ test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner)
+ test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3)
+ test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil)
+ assert.True(t, test1) // owner of org
+ assert.True(t, test2) // user not a part of org
+ assert.False(t, test3) // logged out user
+ })
- const newOrgName = "test-org-private"
- org := &organization.Organization{
- Name: newOrgName,
- Visibility: structs.VisibleTypePrivate,
- }
+ t.Run("Private", func(t *testing.T) {
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
- unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
- assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner))
- org = unittest.AssertExistsAndLoadBean(t,
- &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
- test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner)
- test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3)
- test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil)
- assert.True(t, test1) // owner of org
- assert.False(t, test2) // user not a part of org
- assert.False(t, test3) // logged out user
+ const newOrgName = "test-org-private"
+ org := &organization.Organization{
+ Name: newOrgName,
+ Visibility: structs.VisibleTypePrivate,
+ }
+
+ unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
+ assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner))
+ org = unittest.AssertExistsAndLoadBean(t,
+ &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
+ test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner)
+ test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3)
+ test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil)
+ assert.True(t, test1) // owner of org
+ assert.False(t, test2) // user not a part of org
+ assert.False(t, test3) // logged out user
+ })
}
func TestGetUsersWhoCanCreateOrgRepo(t *testing.T) {
@@ -568,65 +569,65 @@ func TestUser_RemoveOrgRepo(t *testing.T) {
}
func TestCreateOrganization(t *testing.T) {
- // successful creation of org
assert.NoError(t, unittest.PrepareTestDatabase())
- owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- const newOrgName = "neworg"
- org := &organization.Organization{
- Name: newOrgName,
- }
+ t.Run("Unauthorized", func(t *testing.T) {
+ // unauthorized creation of org
- unittest.AssertNotExistsBean(t, &user_model.User{Name: newOrgName, Type: user_model.UserTypeOrganization})
- assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner))
- org = unittest.AssertExistsAndLoadBean(t,
- &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization})
- ownerTeam := unittest.AssertExistsAndLoadBean(t,
- &organization.Team{Name: organization.OwnerTeamName, OrgID: org.ID})
- unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: owner.ID, TeamID: ownerTeam.ID})
- unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
-}
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
+ const newOrgName = "neworg"
+ org := &organization.Organization{
+ Name: newOrgName,
+ }
-func TestCreateOrganization2(t *testing.T) {
- // unauthorized creation of org
- assert.NoError(t, unittest.PrepareTestDatabase())
+ unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization})
+ err := organization.CreateOrganization(t.Context(), org, owner)
+ assert.Error(t, err)
+ assert.True(t, organization.IsErrUserNotAllowedCreateOrg(err))
+ unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization})
+ unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{})
+ })
- owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
- const newOrgName = "neworg"
- org := &organization.Organization{
- Name: newOrgName,
- }
+ t.Run("ExistingName", func(t *testing.T) {
+ // create org with same name as existent org
- unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization})
- err := organization.CreateOrganization(t.Context(), org, owner)
- assert.Error(t, err)
- assert.True(t, organization.IsErrUserNotAllowedCreateOrg(err))
- unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization})
- unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{})
-}
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ org := &organization.Organization{Name: "org3"} // should already exist
+ unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: org.Name}) // sanity check
+ err := organization.CreateOrganization(t.Context(), org, owner)
+ assert.Error(t, err)
+ assert.True(t, user_model.IsErrUserAlreadyExist(err))
+ unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
+ })
-func TestCreateOrganization3(t *testing.T) {
- // create org with same name as existent org
- assert.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("ReservedName", func(t *testing.T) {
+ // create org with unusable name
- owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- org := &organization.Organization{Name: "org3"} // should already exist
- unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: org.Name}) // sanity check
- err := organization.CreateOrganization(t.Context(), org, owner)
- assert.Error(t, err)
- assert.True(t, user_model.IsErrUserAlreadyExist(err))
- unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
-}
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ err := organization.CreateOrganization(t.Context(), &organization.Organization{Name: "assets"}, owner)
+ assert.Error(t, err)
+ assert.True(t, db.IsErrNameReserved(err))
+ unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{})
+ })
-func TestCreateOrganization4(t *testing.T) {
- // create org with unusable name
- assert.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("Success", func(t *testing.T) {
+ // successful creation of org
- owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- err := organization.CreateOrganization(t.Context(), &organization.Organization{Name: "assets"}, owner)
- assert.Error(t, err)
- assert.True(t, db.IsErrNameReserved(err))
- unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{})
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ const newOrgName = "neworg"
+ org := &organization.Organization{
+ Name: newOrgName,
+ }
+
+ unittest.AssertNotExistsBean(t, &user_model.User{Name: newOrgName, Type: user_model.UserTypeOrganization})
+ assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner))
+ org = unittest.AssertExistsAndLoadBean(t,
+ &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization})
+ ownerTeam := unittest.AssertExistsAndLoadBean(t,
+ &organization.Team{Name: organization.OwnerTeamName, OrgID: org.ID})
+ unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: owner.ID, TeamID: ownerTeam.ID})
+ unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
+ })
}
func TestOrAnyRepoUnitPermission(t *testing.T) {
diff --git a/models/organization/team_test.go b/models/organization/team_test.go
index df63c746584..2c16831f249 100644
--- a/models/organization/team_test.go
+++ b/models/organization/team_test.go
@@ -82,7 +82,7 @@ func TestTeam_CanNonMemberReadMeta(t *testing.T) {
func TestTeam_GetRepositories(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- test := func(teamID int64) {
+ for _, teamID := range []int64{1, 3} {
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
repos, err := repo_model.GetTeamRepositories(t.Context(), &repo_model.SearchTeamRepoOptions{
TeamID: team.ID,
@@ -93,14 +93,12 @@ func TestTeam_GetRepositories(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repo.ID})
}
}
- test(1)
- test(3)
}
func TestTeam_GetMembers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- test := func(teamID int64) {
+ for _, teamID := range []int64{1, 3} {
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
assert.NoError(t, team.LoadMembers(t.Context()))
assert.Len(t, team.Members, team.NumMembers)
@@ -108,8 +106,6 @@ func TestTeam_GetMembers(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: member.ID, TeamID: teamID})
}
}
- test(1)
- test(3)
}
func TestGetTeam(t *testing.T) {
@@ -133,15 +129,11 @@ func TestGetTeam(t *testing.T) {
func TestGetTeamByID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- testSuccess := func(teamID int64) {
+ for _, teamID := range []int64{1, 2, 3, 4} {
team, err := organization.GetTeamByID(t.Context(), teamID)
assert.NoError(t, err)
assert.Equal(t, teamID, team.ID)
}
- testSuccess(1)
- testSuccess(2)
- testSuccess(3)
- testSuccess(4)
_, err := organization.GetTeamByID(t.Context(), unittest.NonexistentID)
assert.Error(t, err)
@@ -169,7 +161,7 @@ func TestIsTeamMember(t *testing.T) {
func TestGetTeamMembers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- test := func(teamID int64) {
+ for _, teamID := range []int64{1, 3} {
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
members, err := organization.GetTeamMembers(t.Context(), &organization.SearchMembersOptions{
TeamID: teamID,
@@ -180,22 +172,17 @@ func TestGetTeamMembers(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: member.ID, TeamID: teamID})
}
}
- test(1)
- test(3)
}
func TestGetUserTeams(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- test := func(userID int64) {
+ for _, userID := range []int64{2, 5, unittest.NonexistentID} {
teams, _, err := organization.SearchTeam(t.Context(), &organization.SearchTeamOptions{UserID: userID})
assert.NoError(t, err)
for _, team := range teams {
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{TeamID: team.ID, UID: userID})
}
}
- test(2)
- test(5)
- test(unittest.NonexistentID)
}
func TestGetUserOrgTeams(t *testing.T) {
diff --git a/models/packages/package_test.go b/models/packages/package_test.go
index f6eb5b1184c..46da84e4c05 100644
--- a/models/packages/package_test.go
+++ b/models/packages/package_test.go
@@ -38,29 +38,21 @@ func TestHasOwnerPackages(t *testing.T) {
assert.False(t, has)
assert.NoError(t, err)
- pv, err := packages_model.GetOrInsertVersion(t.Context(), &packages_model.PackageVersion{
- PackageID: p.ID,
- LowerVersion: "internal",
- IsInternal: true,
- })
- assert.NotNil(t, pv)
- assert.NoError(t, err)
+ for _, testCase := range []struct {
+ version packages_model.PackageVersion
+ assertHasPackages assert.BoolAssertionFunc
+ }{
+ // A package with an internal package version gets automatically cleaned up and should return false
+ {packages_model.PackageVersion{PackageID: p.ID, LowerVersion: "internal", IsInternal: true}, assert.False},
+ // A package with a normal package version should return true
+ {packages_model.PackageVersion{PackageID: p.ID, LowerVersion: "normal", IsInternal: false}, assert.True},
+ } {
+ pv, err := packages_model.GetOrInsertVersion(t.Context(), &testCase.version)
+ assert.NotNil(t, pv)
+ assert.NoError(t, err)
- // A package with an internal package version gets automatically cleaned up and should return false
- has, err = packages_model.HasOwnerPackages(t.Context(), owner.ID)
- assert.False(t, has)
- assert.NoError(t, err)
-
- pv, err = packages_model.GetOrInsertVersion(t.Context(), &packages_model.PackageVersion{
- PackageID: p.ID,
- LowerVersion: "normal",
- IsInternal: false,
- })
- assert.NotNil(t, pv)
- assert.NoError(t, err)
-
- // A package with a normal package version should return true
- has, err = packages_model.HasOwnerPackages(t.Context(), owner.ID)
- assert.True(t, has)
- assert.NoError(t, err)
+ has, err = packages_model.HasOwnerPackages(t.Context(), owner.ID)
+ testCase.assertHasPackages(t, has)
+ assert.NoError(t, err)
+ }
}
diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go
index cb076ec98bb..bc96cc37c3f 100644
--- a/models/perm/access/repo_permission_test.go
+++ b/models/perm/access/repo_permission_test.go
@@ -56,43 +56,25 @@ func TestHasAnyUnitAccess(t *testing.T) {
}
func TestApplyPublicAccessRepoPermission(t *testing.T) {
+ for _, test := range []struct {
+ user *user_model.User
+ repoUnit *repo_model.RepoUnit
+ assertRead assert.BoolAssertionFunc
+ }{
+ {nil, &repo_model.RepoUnit{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, assert.False},
+ {nil, &repo_model.RepoUnit{Type: unit.TypeWiki, AnonymousAccessMode: perm_model.AccessModeRead}, assert.True},
+ {&user_model.User{ID: 0}, &repo_model.RepoUnit{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, assert.False},
+ {&user_model.User{ID: 1}, &repo_model.RepoUnit{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, assert.True},
+ } {
+ perm := Permission{
+ AccessMode: perm_model.AccessModeNone,
+ units: []*repo_model.RepoUnit{test.repoUnit},
+ }
+ finalProcessRepoUnitPermission(test.user, &perm)
+ test.assertRead(t, perm.CanRead(unit.TypeWiki))
+ }
+
perm := Permission{
- AccessMode: perm_model.AccessModeNone,
- units: []*repo_model.RepoUnit{
- {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
- },
- }
- finalProcessRepoUnitPermission(nil, &perm)
- assert.False(t, perm.CanRead(unit.TypeWiki))
-
- perm = Permission{
- AccessMode: perm_model.AccessModeNone,
- units: []*repo_model.RepoUnit{
- {Type: unit.TypeWiki, AnonymousAccessMode: perm_model.AccessModeRead},
- },
- }
- finalProcessRepoUnitPermission(nil, &perm)
- assert.True(t, perm.CanRead(unit.TypeWiki))
-
- perm = Permission{
- AccessMode: perm_model.AccessModeNone,
- units: []*repo_model.RepoUnit{
- {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
- },
- }
- finalProcessRepoUnitPermission(&user_model.User{ID: 0}, &perm)
- assert.False(t, perm.CanRead(unit.TypeWiki))
-
- perm = Permission{
- AccessMode: perm_model.AccessModeNone,
- units: []*repo_model.RepoUnit{
- {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
- },
- }
- finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
- assert.True(t, perm.CanRead(unit.TypeWiki))
-
- perm = Permission{
AccessMode: perm_model.AccessModeWrite,
units: []*repo_model.RepoUnit{
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
@@ -117,45 +99,27 @@ func TestApplyPublicAccessRepoPermission(t *testing.T) {
}
func TestUnitAccessMode(t *testing.T) {
- perm := Permission{
- AccessMode: perm_model.AccessModeNone,
+ for _, test := range []struct {
+ accessMode perm_model.AccessMode
+ hasUnit, hasMap bool
+ expected perm_model.AccessMode
+ message string
+ }{
+ {perm_model.AccessModeNone, false, false, perm_model.AccessModeNone, "no unit, no map, use AccessMode"},
+ {perm_model.AccessModeRead, true, false, perm_model.AccessModeRead, "only unit, no map, use AccessMode"},
+ {perm_model.AccessModeAdmin, false, true, perm_model.AccessModeAdmin, "no unit, only map, admin overrides map"},
+ {perm_model.AccessModeNone, false, true, perm_model.AccessModeRead, "no unit, only map, use map"},
+ {perm_model.AccessModeNone, true, true, perm_model.AccessModeRead, "has unit, and map, use map"},
+ } {
+ perm := Permission{AccessMode: test.accessMode}
+ if test.hasUnit {
+ perm.units = []*repo_model.RepoUnit{{Type: unit.TypeWiki}}
+ }
+ if test.hasMap {
+ perm.unitsMode = map[unit.Type]perm_model.AccessMode{unit.TypeWiki: perm_model.AccessModeRead}
+ }
+ assert.Equal(t, test.expected, perm.UnitAccessMode(unit.TypeWiki), test.message)
}
- assert.Equal(t, perm_model.AccessModeNone, perm.UnitAccessMode(unit.TypeWiki), "no unit, no map, use AccessMode")
-
- perm = Permission{
- AccessMode: perm_model.AccessModeRead,
- units: []*repo_model.RepoUnit{
- {Type: unit.TypeWiki},
- },
- }
- assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "only unit, no map, use AccessMode")
-
- perm = Permission{
- AccessMode: perm_model.AccessModeAdmin,
- unitsMode: map[unit.Type]perm_model.AccessMode{
- unit.TypeWiki: perm_model.AccessModeRead,
- },
- }
- assert.Equal(t, perm_model.AccessModeAdmin, perm.UnitAccessMode(unit.TypeWiki), "no unit, only map, admin overrides map")
-
- perm = Permission{
- AccessMode: perm_model.AccessModeNone,
- unitsMode: map[unit.Type]perm_model.AccessMode{
- unit.TypeWiki: perm_model.AccessModeRead,
- },
- }
- assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "no unit, only map, use map")
-
- perm = Permission{
- AccessMode: perm_model.AccessModeNone,
- units: []*repo_model.RepoUnit{
- {Type: unit.TypeWiki},
- },
- unitsMode: map[unit.Type]perm_model.AccessMode{
- unit.TypeWiki: perm_model.AccessModeRead,
- },
- }
- assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map")
}
func TestGetRepoPermission(t *testing.T) {
diff --git a/models/project/project_test.go b/models/project/project_test.go
index b28bca92565..588c67e68fe 100644
--- a/models/project/project_test.go
+++ b/models/project/project_test.go
@@ -34,17 +34,13 @@ func TestIsProjectTypeValid(t *testing.T) {
func TestGetProjects(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- projects, err := db.Find[Project](t.Context(), SearchOptions{RepoID: 1})
- assert.NoError(t, err)
+ for _, repoID := range []int64{1, 3} {
+ projects, err := db.Find[Project](t.Context(), SearchOptions{RepoID: repoID})
+ assert.NoError(t, err)
- // 1 value for this repo exists in the fixtures
- assert.Len(t, projects, 1)
-
- projects, err = db.Find[Project](t.Context(), SearchOptions{RepoID: 3})
- assert.NoError(t, err)
-
- // 1 value for this repo exists in the fixtures
- assert.Len(t, projects, 1)
+ // 1 value for this repo exists in the fixtures
+ assert.Len(t, projects, 1)
+ }
}
func TestProject(t *testing.T) {
diff --git a/models/renderhelper/repo_file_test.go b/models/renderhelper/repo_file_test.go
index 12e1275b2b0..61f1ecd6992 100644
--- a/models/renderhelper/repo_file_test.go
+++ b/models/renderhelper/repo_file_test.go
@@ -98,41 +98,40 @@ func TestRepoFile(t *testing.T) {
`), test.NormalizeHTMLSpaces(rendered))
})
-}
-func TestRepoFileOrgMode(t *testing.T) {
- unittest.PrepareTestEnv(t)
- repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ t.Run("OrgMode", func(t *testing.T) {
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- t.Run("Links", func(t *testing.T) {
- rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{
- CurrentRefSubURL: "/commit/1234",
- CurrentTreePath: "my dir",
- }).WithRelativePath("my dir/a.org")
+ t.Run("Links", func(t *testing.T) {
+ rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{
+ CurrentRefSubURL: "/commit/1234",
+ CurrentTreePath: "my dir",
+ }).WithRelativePath("my dir/a.org")
- rendered, err := testRenderString(rctx, `
+ rendered, err := testRenderString(rctx, `
[[https://google.com/]]
[[ImageLink.svg][The Image Desc]]
`)
- assert.NoError(t, err)
- assert.Equal(t, `
+ assert.NoError(t, err)
+ assert.Equal(t, `
https://google.com/
The Image Desc
`, rendered)
- })
+ })
- t.Run("CodeHighlight", func(t *testing.T) {
- rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{}).WithRelativePath("my-dir/a.org")
+ t.Run("CodeHighlight", func(t *testing.T) {
+ rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{}).WithRelativePath("my-dir/a.org")
- rendered, err := testRenderString(rctx, `
+ rendered, err := testRenderString(rctx, `
#+begin_src c
int a = 1;
#+end_src
`)
- assert.NoError(t, err)
- assert.Equal(t, `
+ assert.NoError(t, err)
+ assert.Equal(t, `
`, rendered)
+ })
})
}
diff --git a/models/renderhelper/simple_document_test.go b/models/renderhelper/simple_document_test.go
index b07c75a259c..5920eab231b 100644
--- a/models/renderhelper/simple_document_test.go
+++ b/models/renderhelper/simple_document_test.go
@@ -6,14 +6,12 @@ package renderhelper
import (
"testing"
- "gitea.dev/models/unittest"
"gitea.dev/modules/markup/markdown"
"github.com/stretchr/testify/assert"
)
func TestSimpleDocument(t *testing.T) {
- unittest.PrepareTestEnv(t)
rctx := NewRenderContextSimpleDocument(t.Context(), "/base").WithMarkupType(markdown.MarkupName)
rendered, err := testRenderString(rctx, `
65f1bf27bc3bf70f64657658635e66094edbcb4d
diff --git a/models/repo/archive_test.go b/models/repo/archive_test.go
deleted file mode 100644
index bb6c1bf9bc2..00000000000
--- a/models/repo/archive_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2025 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package repo
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestArchiveType(t *testing.T) {
- name, archiveType := SplitArchiveNameType("test.tar.gz")
- assert.Equal(t, "test", name)
- assert.Equal(t, "tar.gz", archiveType.String())
-
- name, archiveType = SplitArchiveNameType("a/b/test.zip")
- assert.Equal(t, "a/b/test", name)
- assert.Equal(t, "zip", archiveType.String())
-
- name, archiveType = SplitArchiveNameType("1234.bundle")
- assert.Equal(t, "1234", name)
- assert.Equal(t, "bundle", archiveType.String())
-
- name, archiveType = SplitArchiveNameType("test")
- assert.Equal(t, "test", name)
- assert.Equal(t, "unknown", archiveType.String())
-
- name, archiveType = SplitArchiveNameType("test.xz")
- assert.Equal(t, "test.xz", name)
- assert.Equal(t, "unknown", archiveType.String())
-}
diff --git a/models/repo/attachment_test.go b/models/repo/attachment_test.go
index e5b43f9e5ee..7b1b2dad6ef 100644
--- a/models/repo/attachment_test.go
+++ b/models/repo/attachment_test.go
@@ -34,46 +34,73 @@ func TestIncreaseDownloadCount(t *testing.T) {
func TestGetByCommentOrIssueID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- // count of attachments from issue ID
- attachments, err := repo_model.GetAttachmentsByIssueID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Len(t, attachments, 1)
+ t.Run("GetByCommentOrIssueID", func(t *testing.T) {
+ // count of attachments from issue ID
+ attachments, err := repo_model.GetAttachmentsByIssueID(t.Context(), 1)
+ assert.NoError(t, err)
+ assert.Len(t, attachments, 1)
- attachments, err = repo_model.GetAttachmentsByIssueID(t.Context(), 0)
- assert.NoError(t, err)
- assert.Empty(t, attachments)
+ attachments, err = repo_model.GetAttachmentsByIssueID(t.Context(), 0)
+ assert.NoError(t, err)
+ assert.Empty(t, attachments)
- attachments, err = repo_model.GetAttachmentsByCommentID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Len(t, attachments, 2)
-}
+ attachments, err = repo_model.GetAttachmentsByCommentID(t.Context(), 1)
+ assert.NoError(t, err)
+ assert.Len(t, attachments, 2)
+ })
-func TestDeleteAttachments(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("GetAttachmentsByUUIDs", func(t *testing.T) {
+ attachList, err := repo_model.GetAttachmentsByUUIDs(t.Context(), []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"})
+ assert.NoError(t, err)
+ assert.Len(t, attachList, 2)
+ assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID)
+ assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", attachList[1].UUID)
+ assert.Equal(t, int64(1), attachList[0].IssueID)
+ assert.Equal(t, int64(5), attachList[1].IssueID)
+ })
- count, err := repo_model.DeleteAttachmentsByIssue(t.Context(), 4, false)
- assert.NoError(t, err)
- assert.Equal(t, 2, count)
+ t.Run("GetUnlinkedAttachmentsByUserID", func(t *testing.T) {
+ attachments, err := repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 8)
+ assert.NoError(t, err)
+ assert.Len(t, attachments, 1)
+ assert.Equal(t, int64(10), attachments[0].ID)
+ assert.Zero(t, attachments[0].IssueID)
+ assert.Zero(t, attachments[0].ReleaseID)
+ assert.Zero(t, attachments[0].CommentID)
- count, err = repo_model.DeleteAttachmentsByComment(t.Context(), 2, false)
- assert.NoError(t, err)
- assert.Equal(t, 2, count)
+ attachments, err = repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 1)
+ assert.NoError(t, err)
+ assert.Empty(t, attachments)
+ })
- err = repo_model.DeleteAttachment(t.Context(), &repo_model.Attachment{ID: 8}, false)
- assert.NoError(t, err)
+ t.Run("DeleteAttachments", func(t *testing.T) {
+ count, err := repo_model.DeleteAttachmentsByIssue(t.Context(), 4, false)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, count)
- attachment, err := repo_model.GetAttachmentByUUID(t.Context(), "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18")
- assert.Error(t, err)
- assert.True(t, repo_model.IsErrAttachmentNotExist(err))
- assert.Nil(t, attachment)
-}
+ count, err = repo_model.DeleteAttachmentsByComment(t.Context(), 2, false)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, count)
-func TestGetAttachmentByID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+ err = repo_model.DeleteAttachment(t.Context(), &repo_model.Attachment{ID: 8}, false)
+ assert.NoError(t, err)
- attach, err := repo_model.GetAttachmentByID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID)
+ attachment, err := repo_model.GetAttachmentByUUID(t.Context(), "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18")
+ assert.Error(t, err)
+ assert.True(t, repo_model.IsErrAttachmentNotExist(err))
+ assert.Nil(t, attachment)
+ })
+
+ t.Run("UpdateAttachment", func(t *testing.T) {
+ attach, err := repo_model.GetAttachmentByID(t.Context(), 1)
+ assert.NoError(t, err)
+ assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID)
+
+ attach.Name = "new_name"
+ assert.NoError(t, repo_model.UpdateAttachment(t.Context(), attach))
+
+ unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{Name: "new_name"})
+ })
}
func TestDeleteOrphanedAttachments(t *testing.T) {
@@ -111,44 +138,3 @@ func TestAttachment_DownloadURL(t *testing.T) {
}
assert.Equal(t, "https://try.gitea.io/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.DownloadURL(t.Context()))
}
-
-func TestUpdateAttachment(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- attach, err := repo_model.GetAttachmentByID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID)
-
- attach.Name = "new_name"
- assert.NoError(t, repo_model.UpdateAttachment(t.Context(), attach))
-
- unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{Name: "new_name"})
-}
-
-func TestGetAttachmentsByUUIDs(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- attachList, err := repo_model.GetAttachmentsByUUIDs(t.Context(), []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"})
- assert.NoError(t, err)
- assert.Len(t, attachList, 2)
- assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID)
- assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", attachList[1].UUID)
- assert.Equal(t, int64(1), attachList[0].IssueID)
- assert.Equal(t, int64(5), attachList[1].IssueID)
-}
-
-func TestGetUnlinkedAttachmentsByUserID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- attachments, err := repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 8)
- assert.NoError(t, err)
- assert.Len(t, attachments, 1)
- assert.Equal(t, int64(10), attachments[0].ID)
- assert.Zero(t, attachments[0].IssueID)
- assert.Zero(t, attachments[0].ReleaseID)
- assert.Zero(t, attachments[0].CommentID)
-
- attachments, err = repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 1)
- assert.NoError(t, err)
- assert.Empty(t, attachments)
-}
diff --git a/models/repo/avatar_test.go b/models/repo/avatar_test.go
index 9d895912d77..9f1bd610d09 100644
--- a/models/repo/avatar_test.go
+++ b/models/repo/avatar_test.go
@@ -25,3 +25,19 @@ func TestRepoAvatarLink(t *testing.T) {
link = repo.AvatarLink(t.Context())
assert.Equal(t, "https://localhost/sub-path/repo-avatars/avatar.png", link)
}
+
+func TestArchiveType(t *testing.T) {
+ for _, tc := range []struct {
+ input, name, archiveType string
+ }{
+ {"test.tar.gz", "test", "tar.gz"},
+ {"a/b/test.zip", "a/b/test", "zip"},
+ {"1234.bundle", "1234", "bundle"},
+ {"test", "test", "unknown"},
+ {"test.xz", "test.xz", "unknown"},
+ } {
+ name, archiveType := SplitArchiveNameType(tc.input)
+ assert.Equal(t, tc.name, name)
+ assert.Equal(t, tc.archiveType, archiveType.String())
+ }
+}
diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go
index 5d2045524ae..6318b5b55af 100644
--- a/models/repo/collaboration_test.go
+++ b/models/repo/collaboration_test.go
@@ -15,107 +15,100 @@ import (
func TestRepository_GetCollaborators(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- test := func(repoID int64) {
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
- collaborators, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{RepoID: repo.ID})
- assert.NoError(t, err)
- expectedLen, err := db.GetEngine(t.Context()).Count(&repo_model.Collaboration{RepoID: repoID})
- assert.NoError(t, err)
- assert.Len(t, collaborators, int(expectedLen))
- for _, collaborator := range collaborators {
- assert.Equal(t, collaborator.User.ID, collaborator.Collaboration.UserID)
- assert.Equal(t, repoID, collaborator.Collaboration.RepoID)
+
+ t.Run("Repository_GetCollaborators", func(t *testing.T) {
+ for _, repoID := range []int64{1, 2, 3, 4} {
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
+ collaborators, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{RepoID: repo.ID})
+ assert.NoError(t, err)
+ expectedLen, err := db.GetEngine(t.Context()).Count(&repo_model.Collaboration{RepoID: repoID})
+ assert.NoError(t, err)
+ assert.Len(t, collaborators, int(expectedLen))
+ for _, collaborator := range collaborators {
+ assert.Equal(t, collaborator.User.ID, collaborator.Collaboration.UserID)
+ assert.Equal(t, repoID, collaborator.Collaboration.RepoID)
+ }
}
- }
- test(1)
- test(2)
- test(3)
- test(4)
- // Test db.ListOptions
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
+ // Test db.ListOptions
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
- collaborators1, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{
- ListOptions: db.ListOptions{PageSize: 1, Page: 1},
- RepoID: repo.ID,
- })
- assert.NoError(t, err)
- assert.Len(t, collaborators1, 1)
-
- collaborators2, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{
- ListOptions: db.ListOptions{PageSize: 1, Page: 2},
- RepoID: repo.ID,
- })
- assert.NoError(t, err)
- assert.Len(t, collaborators2, 1)
-
- assert.NotEqual(t, collaborators1[0].ID, collaborators2[0].ID)
-}
-
-func TestRepository_IsCollaborator(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- test := func(repoID, userID int64, expected bool) {
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
- actual, err := repo_model.IsCollaborator(t.Context(), repo.ID, userID)
+ collaborators1, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{
+ ListOptions: db.ListOptions{PageSize: 1, Page: 1},
+ RepoID: repo.ID,
+ })
assert.NoError(t, err)
- assert.Equal(t, expected, actual)
- }
- test(3, 2, true)
- test(3, unittest.NonexistentID, false)
- test(4, 2, false)
- test(4, 4, true)
-}
-
-func TestRepository_IsOwnerMemberCollaborator(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
-
- // Organisation owner.
- actual, err := repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 2)
- assert.NoError(t, err)
- assert.True(t, actual)
-
- // Team member.
- actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 4)
- assert.NoError(t, err)
- assert.True(t, actual)
-
- // Normal user.
- actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 1)
- assert.NoError(t, err)
- assert.False(t, actual)
-
- repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
-
- // Collaborator.
- actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo2, 4)
- assert.NoError(t, err)
- assert.True(t, actual)
-
- repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15})
-
- // Repository owner.
- actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo3, 2)
- assert.NoError(t, err)
- assert.True(t, actual)
-}
-
-func TestRepo_GetCollaboration(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
-
- // Existing collaboration.
- collab, err := repo_model.GetCollaboration(t.Context(), repo.ID, 4)
- assert.NoError(t, err)
- assert.NotNil(t, collab)
- assert.EqualValues(t, 4, collab.UserID)
- assert.EqualValues(t, 4, collab.RepoID)
-
- // Non-existing collaboration.
- collab, err = repo_model.GetCollaboration(t.Context(), repo.ID, 1)
- assert.NoError(t, err)
- assert.Nil(t, collab)
+ assert.Len(t, collaborators1, 1)
+
+ collaborators2, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{
+ ListOptions: db.ListOptions{PageSize: 1, Page: 2},
+ RepoID: repo.ID,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, collaborators2, 1)
+
+ assert.NotEqual(t, collaborators1[0].ID, collaborators2[0].ID)
+ })
+
+ t.Run("Repository_IsCollaborator", func(t *testing.T) {
+ test := func(repoID, userID int64, expected bool) {
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
+ actual, err := repo_model.IsCollaborator(t.Context(), repo.ID, userID)
+ assert.NoError(t, err)
+ assert.Equal(t, expected, actual)
+ }
+ test(3, 2, true)
+ test(3, unittest.NonexistentID, false)
+ test(4, 2, false)
+ test(4, 4, true)
+ })
+
+ t.Run("Repository_IsOwnerMemberCollaborator", func(t *testing.T) {
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
+
+ // Organisation owner.
+ actual, err := repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 2)
+ assert.NoError(t, err)
+ assert.True(t, actual)
+
+ // Team member.
+ actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 4)
+ assert.NoError(t, err)
+ assert.True(t, actual)
+
+ // Normal user.
+ actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 1)
+ assert.NoError(t, err)
+ assert.False(t, actual)
+
+ repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
+
+ // Collaborator.
+ actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo2, 4)
+ assert.NoError(t, err)
+ assert.True(t, actual)
+
+ repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15})
+
+ // Repository owner.
+ actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo3, 2)
+ assert.NoError(t, err)
+ assert.True(t, actual)
+ })
+
+ t.Run("Repo_GetCollaboration", func(t *testing.T) {
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
+
+ // Existing collaboration.
+ collab, err := repo_model.GetCollaboration(t.Context(), repo.ID, 4)
+ assert.NoError(t, err)
+ assert.NotNil(t, collab)
+ assert.EqualValues(t, 4, collab.UserID)
+ assert.EqualValues(t, 4, collab.RepoID)
+
+ // Non-existing collaboration.
+ collab, err = repo_model.GetCollaboration(t.Context(), repo.ID, 1)
+ assert.NoError(t, err)
+ assert.Nil(t, collab)
+ })
}
diff --git a/models/repo/pull_request_default_test.go b/models/repo/pull_request_default_test.go
index 40be8d6573a..55401b05b9d 100644
--- a/models/repo/pull_request_default_test.go
+++ b/models/repo/pull_request_default_test.go
@@ -33,46 +33,37 @@ func TestDefaultTargetBranchSelection(t *testing.T) {
}
func TestPullRequestConfigFromDB(t *testing.T) {
- cases := []struct {
+ for _, tc := range []struct {
// name describes the row shape under test; the comments capture why each row matters.
- name string
- json string
- wantMergeUpdate bool
- wantRebaseUpdate bool
- wantDefaultStyle UpdateStyle
- wantValidatesPass bool
+ name string
+ json string
+ wantRebaseUpdate bool
}{
{
// Empty object exercises the all-defaults path (e.g. fresh repos created via low-level paths).
name: "defaults", json: "{}",
- wantMergeUpdate: true, wantRebaseUpdate: true,
- wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true,
+ wantRebaseUpdate: true,
},
{
// Realistic upgrade case: pre-PR JSON lacks the new fields and has AllowRebaseUpdate=false.
// Historical setting must be preserved while new fields take safe defaults.
- name: "legacy without new fields",
- json: `{"AllowMerge":true,"AllowRebase":true,"AllowRebaseMerge":true,"AllowSquash":true,"AllowRebaseUpdate":false}`,
- wantMergeUpdate: true, wantRebaseUpdate: false,
- wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true,
+ name: "legacy without new fields",
+ json: `{"AllowMerge":true,"AllowRebase":true,"AllowRebaseMerge":true,"AllowSquash":true,"AllowRebaseUpdate":false}`,
+ wantRebaseUpdate: false,
},
{
// Partially-migrated row with explicit empty string must normalize so ValidateUpdateSettings passes.
name: "empty default style", json: `{"DefaultUpdateStyle":""}`,
- wantMergeUpdate: true, wantRebaseUpdate: true,
- wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true,
+ wantRebaseUpdate: true,
},
- }
- for _, tc := range cases {
+ } {
t.Run(tc.name, func(t *testing.T) {
cfg := new(PullRequestsConfig)
assert.NoError(t, cfg.FromDB([]byte(tc.json)))
- assert.Equal(t, tc.wantMergeUpdate, cfg.AllowMergeUpdate)
+ assert.True(t, cfg.AllowMergeUpdate)
assert.Equal(t, tc.wantRebaseUpdate, cfg.AllowRebaseUpdate)
- assert.Equal(t, tc.wantDefaultStyle, cfg.DefaultUpdateStyle)
- if tc.wantValidatesPass {
- assert.NoError(t, cfg.ValidateUpdateSettings())
- }
+ assert.Equal(t, UpdateStyleMerge, cfg.DefaultUpdateStyle)
+ assert.NoError(t, cfg.ValidateUpdateSettings())
})
}
}
diff --git a/models/repo/pushmirror_test.go b/models/repo/pushmirror_test.go
index f4e25e67e52..5630e2c63b4 100644
--- a/models/repo/pushmirror_test.go
+++ b/models/repo/pushmirror_test.go
@@ -27,17 +27,12 @@ func TestPushMirrorsIterate(t *testing.T) {
})
long, _ := time.ParseDuration("24h")
- db.Insert(t.Context(), &repo_model.PushMirror{
- RemoteName: "test-2",
- LastUpdateUnix: now,
- Interval: long,
- })
-
- db.Insert(t.Context(), &repo_model.PushMirror{
- RemoteName: "test-3",
- LastUpdateUnix: now,
- Interval: 0,
- })
+ for _, mirror := range []*repo_model.PushMirror{
+ {RemoteName: "test-2", LastUpdateUnix: now, Interval: long},
+ {RemoteName: "test-3", LastUpdateUnix: now, Interval: 0},
+ } {
+ db.Insert(t.Context(), mirror)
+ }
repo_model.PushMirrorsIterate(t.Context(), 1, func(idx int, bean any) error {
m, ok := bean.(*repo_model.PushMirror)
diff --git a/models/repo/redirect_test.go b/models/repo/redirect_test.go
index bceaf869216..c4bb27f6327 100644
--- a/models/repo/redirect_test.go
+++ b/models/repo/redirect_test.go
@@ -24,53 +24,37 @@ func TestLookupRedirect(t *testing.T) {
}
func TestNewRedirect(t *testing.T) {
- // redirect to a completely new name
- assert.NoError(t, unittest.PrepareTestDatabase())
+ for _, tc := range []struct {
+ repoID int64
+ newName string
+ }{
+ {1, "newreponame"}, // redirect to a completely new name
+ {1, "oldrepo1"}, // redirect to previously used name
+ {2, "newreponame"}, // redirect for a previously-unredirected repo
+ } {
+ t.Run(tc.newName, func(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- assert.NoError(t, repo_model.NewRedirect(t.Context(), repo.OwnerID, repo.ID, repo.Name, "newreponame"))
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: tc.repoID})
+ assert.NoError(t, repo_model.NewRedirect(t.Context(), repo.OwnerID, repo.ID, repo.Name, tc.newName))
- unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{
- OwnerID: repo.OwnerID,
- LowerName: repo.LowerName,
- RedirectRepoID: repo.ID,
- })
- unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{
- OwnerID: repo.OwnerID,
- LowerName: "oldrepo1",
- RedirectRepoID: repo.ID,
- })
-}
-
-func TestNewRedirect2(t *testing.T) {
- // redirect to previously used name
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- assert.NoError(t, repo_model.NewRedirect(t.Context(), repo.OwnerID, repo.ID, repo.Name, "oldrepo1"))
-
- unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{
- OwnerID: repo.OwnerID,
- LowerName: repo.LowerName,
- RedirectRepoID: repo.ID,
- })
- unittest.AssertNotExistsBean(t, &repo_model.Redirect{
- OwnerID: repo.OwnerID,
- LowerName: "oldrepo1",
- RedirectRepoID: repo.ID,
- })
-}
-
-func TestNewRedirect3(t *testing.T) {
- // redirect for a previously-unredirected repo
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
- assert.NoError(t, repo_model.NewRedirect(t.Context(), repo.OwnerID, repo.ID, repo.Name, "newreponame"))
-
- unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{
- OwnerID: repo.OwnerID,
- LowerName: repo.LowerName,
- RedirectRepoID: repo.ID,
- })
+ unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{
+ OwnerID: repo.OwnerID,
+ LowerName: repo.LowerName,
+ RedirectRepoID: repo.ID,
+ })
+ if tc.repoID == 1 {
+ redirect := &repo_model.Redirect{
+ OwnerID: repo.OwnerID,
+ LowerName: "oldrepo1",
+ RedirectRepoID: repo.ID,
+ }
+ if tc.newName == "oldrepo1" {
+ unittest.AssertNotExistsBean(t, redirect)
+ } else {
+ unittest.AssertExistsAndLoadBean(t, redirect)
+ }
+ }
+ })
+ }
}
diff --git a/models/repo/release_test.go b/models/repo/release_test.go
index 888af26fea6..f3de84be468 100644
--- a/models/repo/release_test.go
+++ b/models/repo/release_test.go
@@ -30,75 +30,69 @@ func TestMigrate_InsertReleases(t *testing.T) {
func Test_FindTagsByCommitIDs(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- sha1Rels, err := FindTagsByCommitIDs(t.Context(), 1, "65f1bf27bc3bf70f64657658635e66094edbcb4d")
- assert.NoError(t, err)
- assert.Len(t, sha1Rels, 1)
- rels := sha1Rels["65f1bf27bc3bf70f64657658635e66094edbcb4d"]
- assert.Len(t, rels, 3)
- assert.Equal(t, "v1.1", rels[0].TagName)
- assert.Equal(t, "delete-tag", rels[1].TagName)
- assert.Equal(t, "v1.0", rels[2].TagName)
-}
-
-func TestAddReleaseAttachmentsRejectsDifferentRepo(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12" // attachment 2 belongs to repo 2
- err := AddReleaseAttachments(t.Context(), 1, []string{uuid})
- assert.Error(t, err)
- assert.ErrorIs(t, err, util.ErrPermissionDenied)
-
- attach, err := GetAttachmentByUUID(t.Context(), uuid)
- assert.NoError(t, err)
- assert.Zero(t, attach.ReleaseID, "attachment should not be linked to release on failure")
-}
-
-func TestAddReleaseAttachmentsRejectsIssueAttachment(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
- err := AddReleaseAttachments(t.Context(), 1, []string{uuid})
- assert.ErrorIs(t, err, util.ErrPermissionDenied)
-
- attach, err := GetAttachmentByUUID(t.Context(), uuid)
- assert.NoError(t, err)
- assert.Zero(t, attach.ReleaseID)
-}
-
-func TestAddReleaseAttachmentsAllowsLegacyMissingRepoID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- legacyUUID := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20" // attachment 10 has repo_id 0
- err := AddReleaseAttachments(t.Context(), 1, []string{legacyUUID})
- assert.NoError(t, err)
-
- attach, err := GetAttachmentByUUID(t.Context(), legacyUUID)
- assert.NoError(t, err)
- assert.EqualValues(t, 1, attach.RepoID)
- assert.EqualValues(t, 1, attach.ReleaseID)
-}
-
-func TestAddReleaseAttachmentsRejectsRecentZeroRepoID(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- recentUUID := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd3800aa"
- attachment := &Attachment{
- UUID: recentUUID,
- RepoID: 0,
- IssueID: 0,
- ReleaseID: 0,
- CommentID: 0,
- Name: "recent-zero",
- CreatedUnix: LegacyAttachmentMissingRepoIDCutoff + 1,
- }
- assert.NoError(t, db.Insert(t.Context(), attachment))
-
- err := AddReleaseAttachments(t.Context(), 1, []string{recentUUID})
- assert.Error(t, err)
- assert.ErrorIs(t, err, util.ErrPermissionDenied)
-
- attach, err := GetAttachmentByUUID(t.Context(), recentUUID)
- assert.NoError(t, err)
- assert.Zero(t, attach.ReleaseID)
- assert.Zero(t, attach.RepoID)
+ t.Run("_FindTagsByCommitIDs", func(t *testing.T) {
+ sha1Rels, err := FindTagsByCommitIDs(t.Context(), 1, "65f1bf27bc3bf70f64657658635e66094edbcb4d")
+ assert.NoError(t, err)
+ assert.Len(t, sha1Rels, 1)
+ rels := sha1Rels["65f1bf27bc3bf70f64657658635e66094edbcb4d"]
+ assert.Len(t, rels, 3)
+ assert.Equal(t, "v1.1", rels[0].TagName)
+ assert.Equal(t, "delete-tag", rels[1].TagName)
+ assert.Equal(t, "v1.0", rels[2].TagName)
+ })
+
+ t.Run("AddReleaseAttachmentsRejectsDifferentRepo", func(t *testing.T) {
+ uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12" // attachment 2 belongs to repo 2
+ err := AddReleaseAttachments(t.Context(), 1, []string{uuid})
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrPermissionDenied)
+
+ attach, err := GetAttachmentByUUID(t.Context(), uuid)
+ assert.NoError(t, err)
+ assert.Zero(t, attach.ReleaseID, "attachment should not be linked to release on failure")
+ })
+
+ t.Run("AddReleaseAttachmentsRejectsIssueAttachment", func(t *testing.T) {
+ uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
+ err := AddReleaseAttachments(t.Context(), 1, []string{uuid})
+ assert.ErrorIs(t, err, util.ErrPermissionDenied)
+
+ attach, err := GetAttachmentByUUID(t.Context(), uuid)
+ assert.NoError(t, err)
+ assert.Zero(t, attach.ReleaseID)
+ })
+
+ t.Run("AddReleaseAttachmentsRejectsRecentZeroRepoID", func(t *testing.T) {
+ recentUUID := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd3800aa"
+ attachment := &Attachment{
+ UUID: recentUUID,
+ RepoID: 0,
+ IssueID: 0,
+ ReleaseID: 0,
+ CommentID: 0,
+ Name: "recent-zero",
+ CreatedUnix: LegacyAttachmentMissingRepoIDCutoff + 1,
+ }
+ assert.NoError(t, db.Insert(t.Context(), attachment))
+
+ err := AddReleaseAttachments(t.Context(), 1, []string{recentUUID})
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrPermissionDenied)
+
+ attach, err := GetAttachmentByUUID(t.Context(), recentUUID)
+ assert.NoError(t, err)
+ assert.Zero(t, attach.ReleaseID)
+ assert.Zero(t, attach.RepoID)
+ })
+
+ t.Run("AddReleaseAttachmentsAllowsLegacyMissingRepoID", func(t *testing.T) {
+ legacyUUID := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20" // attachment 10 has repo_id 0
+ err := AddReleaseAttachments(t.Context(), 1, []string{legacyUUID})
+ assert.NoError(t, err)
+
+ attach, err := GetAttachmentByUUID(t.Context(), legacyUUID)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, attach.RepoID)
+ assert.EqualValues(t, 1, attach.ReleaseID)
+ })
}
diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go
index e36cfa2b517..a0709a66460 100644
--- a/models/repo/repo_test.go
+++ b/models/repo/repo_test.go
@@ -96,27 +96,20 @@ func TestMetas(t *testing.T) {
},
}
- testSuccess := func(expectedStyle string) {
+ for _, style := range []string{"", markup.IssueNameStyleAlphanumeric, markup.IssueNameStyleNumeric, markup.IssueNameStyleRegexp} {
+ externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = style
repo.Units = []*RepoUnit{&externalTracker}
repo.commonRenderingMetas = nil
metas := repo.ComposeCommentMetas(t.Context())
- assert.Equal(t, expectedStyle, metas["style"])
+ if style == "" {
+ style = markup.IssueNameStyleNumeric
+ }
+ assert.Equal(t, style, metas["style"])
assert.Equal(t, "testRepo", metas["repo"])
assert.Equal(t, "testOwner", metas["user"])
assert.Equal(t, "https://someurl.com/{user}/{repo}/{issue}", metas["externalTrackerLinkFormat"])
}
- testSuccess(markup.IssueNameStyleNumeric)
-
- externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleAlphanumeric
- testSuccess(markup.IssueNameStyleAlphanumeric)
-
- externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleNumeric
- testSuccess(markup.IssueNameStyleNumeric)
-
- externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
- testSuccess(markup.IssueNameStyleRegexp)
-
repo, err := GetRepositoryByID(t.Context(), 3)
assert.NoError(t, err)
@@ -211,29 +204,21 @@ func TestComposeSSHCloneURL(t *testing.T) {
}
func TestIsUsableRepoName(t *testing.T) {
- assert.NoError(t, IsUsableRepoName("a"))
- assert.NoError(t, IsUsableRepoName("-1_."))
- assert.NoError(t, IsUsableRepoName(".profile"))
+ for _, name := range []string{"a", "-1_.", ".profile"} {
+ assert.NoError(t, IsUsableRepoName(name))
+ }
- assert.Error(t, IsUsableRepoName("-"))
- assert.Error(t, IsUsableRepoName("🌞"))
- assert.Error(t, IsUsableRepoName("the/repo"))
- assert.Error(t, IsUsableRepoName("the..repo"))
- assert.Error(t, IsUsableRepoName("foo.wiki"))
- assert.Error(t, IsUsableRepoName("foo.git"))
- assert.Error(t, IsUsableRepoName("foo.RSS"))
+ for _, name := range []string{"-", "🌞", "the/repo", "the..repo", "foo.wiki", "foo.git", "foo.RSS"} {
+ assert.Error(t, IsUsableRepoName(name))
+ }
}
func TestIsValidSSHAccessRepoName(t *testing.T) {
- assert.True(t, IsValidSSHAccessRepoName("a"))
- assert.True(t, IsValidSSHAccessRepoName("-1_."))
- assert.True(t, IsValidSSHAccessRepoName(".profile"))
- assert.True(t, IsValidSSHAccessRepoName("foo.wiki"))
+ for _, name := range []string{"a", "-1_.", ".profile", "foo.wiki"} {
+ assert.True(t, IsValidSSHAccessRepoName(name))
+ }
- assert.False(t, IsValidSSHAccessRepoName("-"))
- assert.False(t, IsValidSSHAccessRepoName("🌞"))
- assert.False(t, IsValidSSHAccessRepoName("the/repo"))
- assert.False(t, IsValidSSHAccessRepoName("the..repo"))
- assert.False(t, IsValidSSHAccessRepoName("foo.git"))
- assert.False(t, IsValidSSHAccessRepoName("foo.RSS"))
+ for _, name := range []string{"-", "🌞", "the/repo", "the..repo", "foo.git", "foo.RSS"} {
+ assert.False(t, IsValidSSHAccessRepoName(name))
+ }
}
diff --git a/models/repo/star_test.go b/models/repo/star_test.go
index d81c3a1685b..df38abad20e 100644
--- a/models/repo/star_test.go
+++ b/models/repo/star_test.go
@@ -17,6 +17,29 @@ import (
func TestStarRepo(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("IsStaring", func(t *testing.T) {
+ assert.True(t, repo_model.IsStaring(t.Context(), 2, 4))
+ assert.False(t, repo_model.IsStaring(t.Context(), 3, 4))
+ })
+
+ t.Run("Repository_GetStargazers", func(t *testing.T) {
+ // repo with stargazers
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
+ gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0})
+ assert.NoError(t, err)
+ if assert.Len(t, gazers, 1) {
+ assert.Equal(t, int64(2), gazers[0].ID)
+ }
+ })
+
+ t.Run("Repository_GetStargazers2", func(t *testing.T) {
+ // repo with stargazers
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
+ gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0})
+ assert.NoError(t, err)
+ assert.Empty(t, gazers)
+ })
+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
@@ -27,49 +50,21 @@ func TestStarRepo(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, false))
unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
-}
-
-func TestIsStaring(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- assert.True(t, repo_model.IsStaring(t.Context(), 2, 4))
- assert.False(t, repo_model.IsStaring(t.Context(), 3, 4))
-}
-
-func TestRepository_GetStargazers(t *testing.T) {
- // repo with stargazers
- assert.NoError(t, unittest.PrepareTestDatabase())
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
- gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0})
- assert.NoError(t, err)
- if assert.Len(t, gazers, 1) {
- assert.Equal(t, int64(2), gazers[0].ID)
- }
-}
-
-func TestRepository_GetStargazers2(t *testing.T) {
- // repo with stargazers
- assert.NoError(t, unittest.PrepareTestDatabase())
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
- gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0})
- assert.NoError(t, err)
- assert.Empty(t, gazers)
-}
-
-func TestClearRepoStars(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
-
- unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
- assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, true))
- unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
- assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, false))
- unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
- assert.NoError(t, repo_model.ClearRepoStars(t.Context(), repo.ID))
- unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
-
- gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0})
- assert.NoError(t, err)
- assert.Empty(t, gazers)
+
+ t.Run("ClearRepoStars", func(t *testing.T) {
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+
+ unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
+ assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, true))
+ unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
+ assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, false))
+ unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
+ assert.NoError(t, repo_model.ClearRepoStars(t.Context(), repo.ID))
+ unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID})
+
+ gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0})
+ assert.NoError(t, err)
+ assert.Empty(t, gazers)
+ })
}
diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go
index bf1388f985b..614a733c622 100644
--- a/models/repo/watch_test.go
+++ b/models/repo/watch_test.go
@@ -72,47 +72,26 @@ func TestWatchIfAuto(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, watchers, repo.NumWatches)
- setting.Service.AutoWatchOnChanges = false
-
prevCount := repo.NumWatches
-
- // Must not add watch
- assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 8, 1, true))
- watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
- assert.NoError(t, err)
- assert.Len(t, watchers, prevCount)
-
- // Should not add watch
- assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 10, 1, true))
- watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
- assert.NoError(t, err)
- assert.Len(t, watchers, prevCount)
-
- setting.Service.AutoWatchOnChanges = true
-
- // Must not add watch (user 8 has "don't watch" set)
- assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 8, 1, true))
- watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
- assert.NoError(t, err)
- assert.Len(t, watchers, prevCount)
-
- // Should not add watch (read operation)
- assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 12, 1, false))
- watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
- assert.NoError(t, err)
- assert.Len(t, watchers, prevCount)
-
- // Should add watch (write operation)
- assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 12, 1, true))
- watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
- assert.NoError(t, err)
- assert.Len(t, watchers, prevCount+1)
-
- // Should not add watch (system user should not be able to watch)
- assert.NoError(t, repo_model.WatchIfAuto(t.Context(), user_model.ActionsUserID, 1, true))
- watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
- assert.NoError(t, err)
- assert.Len(t, watchers, prevCount+1)
+ for _, test := range []struct {
+ autoWatch bool
+ userID int64
+ isWrite bool
+ count int
+ }{
+ {false, 8, true, prevCount}, // Must not add watch
+ {false, 10, true, prevCount}, // Should not add watch
+ {true, 8, true, prevCount}, // Must not add watch (user 8 has "don't watch" set)
+ {true, 12, false, prevCount}, // Should not add watch (read operation)
+ {true, 12, true, prevCount + 1}, // Should add watch (write operation)
+ {true, user_model.ActionsUserID, true, prevCount + 1}, // Should not add watch (system user should not be able to watch)
+ } {
+ setting.Service.AutoWatchOnChanges = test.autoWatch
+ assert.NoError(t, repo_model.WatchIfAuto(t.Context(), test.userID, 1, test.isWrite))
+ watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1})
+ assert.NoError(t, err)
+ assert.Len(t, watchers, test.count)
+ }
// Should remove watch, inhibit from adding auto
assert.NoError(t, repo_model.WatchRepoAuto(t.Context(), user12, repo, false))
diff --git a/models/secret/secret_test.go b/models/secret/secret_test.go
index 11dd0f750cd..8d1d2d1e371 100644
--- a/models/secret/secret_test.go
+++ b/models/secret/secret_test.go
@@ -40,135 +40,80 @@ func TestGetScopedSecretsForJob(t *testing.T) {
return job
}
- t.Run("TopLevelJob_ReturnsBaseUnchanged", func(t *testing.T) {
- const runID = 9001
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: 0}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, base, got, "top-level jobs should see the full base scope")
- })
-
- t.Run("CallerInherit_PassesParentScopeThrough", func(t *testing.T) {
- const runID = 9002
- caller := insertCaller(t, runID, 0, "inherit")
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, base, got, "secrets: inherit forwards everything from parent scope")
- })
-
- t.Run("CallerEmptySecrets_ExposesOnlyAutoTokens", func(t *testing.T) {
- const runID = 9003
- caller := insertCaller(t, runID, 0, "")
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, map[string]string{
- "GITHUB_TOKEN": "tok",
- "GITEA_TOKEN": "tok",
- }, got)
- })
-
- t.Run("CallerMapping_OnlyMappedAliasesPlusTokens", func(t *testing.T) {
- const runID = 9004
+ for _, testCase := range []struct {
+ name string
+ runID int64
+ callSecrets []string
+ check func(*testing.T, map[string]string)
+ }{
+ {"TopLevelJob_ReturnsBaseUnchanged", 9001, nil, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, base, got, "top-level jobs should see the full base scope")
+ }},
+ {"CallerInherit_PassesParentScopeThrough", 9002, []string{"inherit"}, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, base, got, "secrets: inherit forwards everything from parent scope")
+ }},
+ {"CallerEmptySecrets_ExposesOnlyAutoTokens", 9003, []string{""}, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, map[string]string{
+ "GITHUB_TOKEN": "tok",
+ "GITEA_TOKEN": "tok",
+ }, got)
+ }},
// {alias: source} - the called workflow sees `secrets.MY_KEY` resolved to PROD_API_KEY's value.
- caller := insertCaller(t, runID, 0, `{"MY_KEY":"PROD_API_KEY"}`)
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, map[string]string{
- "GITHUB_TOKEN": "tok",
- "GITEA_TOKEN": "tok",
- "MY_KEY": "prod-secret",
- // no "dev-secret"
- }, got)
- })
-
- t.Run("CallerMapping_CaseInsensitiveSource", func(t *testing.T) {
- const runID = 9005
- caller := insertCaller(t, runID, 0, `{"alias":"prod_api_key"}`)
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, "prod-secret", got["alias"])
- })
-
- t.Run("CallerMapping_UnknownSourceDropsAlias", func(t *testing.T) {
- const runID = 9006
+ {"CallerMapping_OnlyMappedAliasesPlusTokens", 9004, []string{`{"MY_KEY":"PROD_API_KEY"}`}, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, map[string]string{
+ "GITHUB_TOKEN": "tok",
+ "GITEA_TOKEN": "tok",
+ "MY_KEY": "prod-secret",
+ // no "dev-secret"
+ }, got)
+ }},
+ {"CallerMapping_CaseInsensitiveSource", 9005, []string{`{"alias":"prod_api_key"}`}, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, "prod-secret", got["alias"])
+ }},
// alias points at a non-existent secret name, so it must be dropped.
- caller := insertCaller(t, runID, 0, `{"MAPPED_ALIAS":"DOES_NOT_EXIST"}`)
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- _, present := got["MAPPED_ALIAS"]
- assert.False(t, present)
- })
-
- t.Run("Nested_InheritThenInherit_FullScope", func(t *testing.T) {
- const runID = 9007
- outer := insertCaller(t, runID, 0, "inherit")
- inner := insertCaller(t, runID, outer.ID, "inherit")
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: inner.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, base, got, "inherit-then-inherit should pass the full base scope through")
- })
-
- t.Run("Nested_InheritThenMapping_InnerNarrows", func(t *testing.T) {
- const runID = 9008
+ {"CallerMapping_UnknownSourceDropsAlias", 9006, []string{`{"MAPPED_ALIAS":"DOES_NOT_EXIST"}`}, func(t *testing.T, got map[string]string) {
+ _, present := got["MAPPED_ALIAS"]
+ assert.False(t, present)
+ }},
+ {"Nested_InheritThenInherit_FullScope", 9007, []string{"inherit", "inherit"}, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, base, got, "inherit-then-inherit should pass the full base scope through")
+ }},
// inner mapping narrows the full scope it inherited from outer.
- outer := insertCaller(t, runID, 0, "inherit")
- inner := insertCaller(t, runID, outer.ID, `{"ALIAS_OUT":"PROD_API_KEY"}`)
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: inner.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, map[string]string{
- "GITHUB_TOKEN": "tok",
- "GITEA_TOKEN": "tok",
- "ALIAS_OUT": "prod-secret",
- // no "dev-secret"
- }, got)
- })
-
- t.Run("Nested_MappingThenInherit_OuterNarrows", func(t *testing.T) {
- const runID = 9009
+ {"Nested_InheritThenMapping_InnerNarrows", 9008, []string{"inherit", `{"ALIAS_OUT":"PROD_API_KEY"}`}, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, map[string]string{
+ "GITHUB_TOKEN": "tok",
+ "GITEA_TOKEN": "tok",
+ "ALIAS_OUT": "prod-secret",
+ // no "dev-secret"
+ }, got)
+ }},
// inner inherits outer's already-narrowed scope, so leaf sees only auto-tokens + OUTER_ALIAS.
- outer := insertCaller(t, runID, 0, `{"OUTER_ALIAS":"PROD_API_KEY"}`)
- inner := insertCaller(t, runID, outer.ID, "inherit")
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: inner.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, map[string]string{
- "GITHUB_TOKEN": "tok",
- "GITEA_TOKEN": "tok",
- "OUTER_ALIAS": "prod-secret",
- // no "dev-secret"
- }, got)
- })
-
- t.Run("Nested_MappingThenMapping_InnerSourceMustExistInOuterScope", func(t *testing.T) {
- const runID = 9010
+ {"Nested_MappingThenInherit_OuterNarrows", 9009, []string{`{"OUTER_ALIAS":"PROD_API_KEY"}`, "inherit"}, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, map[string]string{
+ "GITHUB_TOKEN": "tok",
+ "GITEA_TOKEN": "tok",
+ "OUTER_ALIAS": "prod-secret",
+ // no "dev-secret"
+ }, got)
+ }},
// inner can rename ALIAS_A (in outer's scope) to ALIAS_C, but cannot forward DEV_API_KEY, which outer dropped.
- outer := insertCaller(t, runID, 0, `{"ALIAS_A":"PROD_API_KEY"}`)
- inner := insertCaller(t, runID, outer.ID, `{"ALIAS_B":"DEV_API_KEY","ALIAS_C":"ALIAS_A"}`)
- leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: inner.ID}
-
- got, err := getScopedSecretsForJob(ctx, leaf, base)
- require.NoError(t, err)
- assert.Equal(t, map[string]string{
- "GITHUB_TOKEN": "tok",
- "GITEA_TOKEN": "tok",
- "ALIAS_C": "prod-secret",
- // no "dev-secret"
- }, got)
- })
+ {"Nested_MappingThenMapping_InnerSourceMustExistInOuterScope", 9010, []string{`{"ALIAS_A":"PROD_API_KEY"}`, `{"ALIAS_B":"DEV_API_KEY","ALIAS_C":"ALIAS_A"}`}, func(t *testing.T, got map[string]string) {
+ assert.Equal(t, map[string]string{
+ "GITHUB_TOKEN": "tok",
+ "GITEA_TOKEN": "tok",
+ "ALIAS_C": "prod-secret",
+ // no "dev-secret"
+ }, got)
+ }},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ leaf := &actions_model.ActionRunJob{RunID: testCase.runID, ParentJobID: 0}
+ for _, callSecrets := range testCase.callSecrets {
+ leaf.ParentJobID = insertCaller(t, testCase.runID, leaf.ParentJobID, callSecrets).ID
+ }
+ got, err := getScopedSecretsForJob(ctx, leaf, base)
+ require.NoError(t, err)
+ testCase.check(t, got)
+ })
+ }
}
diff --git a/models/system/setting_test.go b/models/system/setting_test.go
index c319e2b54dd..cccbb46dfa3 100644
--- a/models/system/setting_test.go
+++ b/models/system/setting_test.go
@@ -24,21 +24,15 @@ func TestSettings(t *testing.T) {
assert.Equal(t, 1, rev)
assert.Len(t, settings, 1) // there is only one "revision" key
- err = system.SetSettings(t.Context(), map[string]string{keyName: "true"})
- assert.NoError(t, err)
- rev, settings, err = system.GetAllSettings(t.Context())
- assert.NoError(t, err)
- assert.Equal(t, 2, rev)
- assert.Len(t, settings, 2)
- assert.Equal(t, "true", settings[keyName])
-
- err = system.SetSettings(t.Context(), map[string]string{keyName: "false"})
- assert.NoError(t, err)
- rev, settings, err = system.GetAllSettings(t.Context())
- assert.NoError(t, err)
- assert.Equal(t, 3, rev)
- assert.Len(t, settings, 2)
- assert.Equal(t, "false", settings[keyName])
+ for index, value := range []string{"true", "false"} {
+ err = system.SetSettings(t.Context(), map[string]string{keyName: value})
+ assert.NoError(t, err)
+ rev, settings, err = system.GetAllSettings(t.Context())
+ assert.NoError(t, err)
+ assert.Equal(t, index+2, rev)
+ assert.Len(t, settings, 2)
+ assert.Equal(t, value, settings[keyName])
+ }
// setting the same value should not trigger DuplicateKey error, and the "version" should be increased
err = system.SetSettings(t.Context(), map[string]string{keyName: "false"})
diff --git a/models/unit/unit_test.go b/models/unit/unit_test.go
index 5c69ec21ba1..c96b831c287 100644
--- a/models/unit/unit_test.go
+++ b/models/unit/unit_test.go
@@ -24,39 +24,26 @@ func mockUnitConfig(t *testing.T) {
}
func TestLoadUnitConfig(t *testing.T) {
- t.Run("regular", func(t *testing.T) {
- mockUnitConfig(t)
+ for _, testCase := range []struct {
+ name string
+ disabled, defaults, forks []string
+ }{
+ {"regular", []string{"repo.issues"}, []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"}, []string{"repo.releases"}},
+ {"invalid", []string{"repo.issues", "invalid.1"}, []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"}, []string{"invalid.3", "repo.releases"}},
+ {"duplicate", []string{"repo.issues", "repo.issues"}, []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"}, []string{"repo.releases", "repo.releases"}},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ mockUnitConfig(t)
- setting.Repository.DisabledRepoUnits = []string{"repo.issues"}
- setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"}
- setting.Repository.DefaultForkRepoUnits = []string{"repo.releases"}
- assert.NoError(t, LoadUnitConfig())
- assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
- assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
- assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
- })
- t.Run("invalid", func(t *testing.T) {
- mockUnitConfig(t)
-
- setting.Repository.DisabledRepoUnits = []string{"repo.issues", "invalid.1"}
- setting.Repository.DefaultRepoUnits = []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"}
- setting.Repository.DefaultForkRepoUnits = []string{"invalid.3", "repo.releases"}
- assert.NoError(t, LoadUnitConfig())
- assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
- assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
- assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
- })
- t.Run("duplicate", func(t *testing.T) {
- mockUnitConfig(t)
-
- setting.Repository.DisabledRepoUnits = []string{"repo.issues", "repo.issues"}
- setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"}
- setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
- assert.NoError(t, LoadUnitConfig())
- assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
- assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
- assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
- })
+ setting.Repository.DisabledRepoUnits = testCase.disabled
+ setting.Repository.DefaultRepoUnits = testCase.defaults
+ setting.Repository.DefaultForkRepoUnits = testCase.forks
+ assert.NoError(t, LoadUnitConfig())
+ assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
+ assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
+ assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
+ })
+ }
t.Run("empty_default", func(t *testing.T) {
mockUnitConfig(t)
diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go
index 15e2d84b2e9..e54fe09dd41 100644
--- a/models/user/email_address_test.go
+++ b/models/user/email_address_test.go
@@ -15,138 +15,117 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestGetEmailAddresses(t *testing.T) {
+func TestEmailAddresses(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- emails, _ := user_model.GetEmailAddresses(t.Context(), int64(1))
- if assert.Len(t, emails, 3) {
+ t.Run("GetEmailAddresses", func(t *testing.T) {
+ emails, _ := user_model.GetEmailAddresses(t.Context(), int64(1))
+ if assert.Len(t, emails, 3) {
+ assert.True(t, emails[0].IsPrimary)
+ assert.True(t, emails[2].IsActivated)
+ assert.False(t, emails[2].IsPrimary)
+ }
+
+ emails, _ = user_model.GetEmailAddresses(t.Context(), int64(2))
+ if assert.Len(t, emails, 2) {
+ assert.True(t, emails[0].IsPrimary)
+ assert.True(t, emails[0].IsActivated)
+ }
+ })
+
+ t.Run("IsEmailUsed", func(t *testing.T) {
+ isExist, _ := user_model.IsEmailUsed(t.Context(), "")
+ assert.True(t, isExist)
+ isExist, _ = user_model.IsEmailUsed(t.Context(), "user11@example.com")
+ assert.True(t, isExist)
+ isExist, _ = user_model.IsEmailUsed(t.Context(), "user1234567890@example.com")
+ assert.False(t, isExist)
+ })
+
+ t.Run("ListEmails", func(t *testing.T) {
+ search := func(opts *user_model.SearchEmailOptions) ([]*user_model.SearchEmailResult, int64) {
+ emails, count, err := user_model.SearchEmails(t.Context(), opts)
+ assert.NoError(t, err)
+ return emails, count
+ }
+
+ // Must find all users and their emails
+ emails, count := search(&user_model.SearchEmailOptions{ListOptions: db.ListOptions{PageSize: 10000}})
+ assert.Greater(t, count, int64(5))
+
+ contains := func(match func(s *user_model.SearchEmailResult) bool) bool {
+ return slices.ContainsFunc(emails, match)
+ }
+
+ assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 }))
+ // 'org3' is an organization
+ assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 3 }))
+
+ // Must find no records
+ emails, count = search(&user_model.SearchEmailOptions{Keyword: "NOTFOUND"})
+ assert.Equal(t, int64(0), count)
+
+ // Must find users 'user2', 'user28', etc.
+ emails, count = search(&user_model.SearchEmailOptions{Keyword: "user2"})
+ assert.NotEqual(t, int64(0), count)
+ assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 2 }))
+ assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 27 }))
+
+ // Must find only primary addresses (i.e. from the `user` table)
+ emails, _ = search(&user_model.SearchEmailOptions{IsPrimary: optional.Some(true)})
+ assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary }))
+ assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary }))
+
+ // Must find only inactive addresses (i.e. not validated)
+ emails, _ = search(&user_model.SearchEmailOptions{IsActivated: optional.Some(false)})
+ assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated }))
+ assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsActivated }))
+
+ // Must find more than one page, but retrieve only one
+ emails, count = search(&user_model.SearchEmailOptions{ListOptions: db.ListOptions{PageSize: 5, Page: 1}})
+ assert.Len(t, emails, 5)
+ assert.Greater(t, count, int64(len(emails)))
+ })
+
+ t.Run("MakeEmailPrimary", func(t *testing.T) {
+ err := user_model.MakeActiveEmailPrimary(t.Context(), 1, 9999999)
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{})
+
+ email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user11@example.com"})
+ err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID)
+ assert.Error(t, err)
+ assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{}) // inactive email is considered as not exist for "MakeActiveEmailPrimary"
+
+ email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user9999999@example.com"})
+ err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID)
+ assert.Error(t, err)
+ assert.True(t, user_model.IsErrUserNotExist(err))
+
+ email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user101@example.com"})
+ err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID)
+ assert.NoError(t, err)
+
+ user, _ := user_model.GetUserByID(t.Context(), int64(10))
+ assert.Equal(t, "user101@example.com", user.Email)
+ })
+
+ t.Run("Activate", func(t *testing.T) {
+ email := &user_model.EmailAddress{
+ ID: int64(1),
+ UID: int64(1),
+ Email: "user11@example.com",
+ }
+ assert.NoError(t, user_model.ActivateEmail(t.Context(), email))
+
+ emails, _ := user_model.GetEmailAddresses(t.Context(), int64(1))
+ assert.Len(t, emails, 3)
+ assert.True(t, emails[0].IsActivated)
assert.True(t, emails[0].IsPrimary)
+ assert.False(t, emails[1].IsPrimary)
assert.True(t, emails[2].IsActivated)
assert.False(t, emails[2].IsPrimary)
- }
-
- emails, _ = user_model.GetEmailAddresses(t.Context(), int64(2))
- if assert.Len(t, emails, 2) {
- assert.True(t, emails[0].IsPrimary)
- assert.True(t, emails[0].IsActivated)
- }
-}
-
-func TestIsEmailUsed(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- isExist, _ := user_model.IsEmailUsed(t.Context(), "")
- assert.True(t, isExist)
- isExist, _ = user_model.IsEmailUsed(t.Context(), "user11@example.com")
- assert.True(t, isExist)
- isExist, _ = user_model.IsEmailUsed(t.Context(), "user1234567890@example.com")
- assert.False(t, isExist)
-}
-
-func TestMakeEmailPrimary(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- err := user_model.MakeActiveEmailPrimary(t.Context(), 1, 9999999)
- assert.Error(t, err)
- assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{})
-
- email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user11@example.com"})
- err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID)
- assert.Error(t, err)
- assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{}) // inactive email is considered as not exist for "MakeActiveEmailPrimary"
-
- email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user9999999@example.com"})
- err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID)
- assert.Error(t, err)
- assert.True(t, user_model.IsErrUserNotExist(err))
-
- email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user101@example.com"})
- err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID)
- assert.NoError(t, err)
-
- user, _ := user_model.GetUserByID(t.Context(), int64(10))
- assert.Equal(t, "user101@example.com", user.Email)
-}
-
-func TestActivate(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- email := &user_model.EmailAddress{
- ID: int64(1),
- UID: int64(1),
- Email: "user11@example.com",
- }
- assert.NoError(t, user_model.ActivateEmail(t.Context(), email))
-
- emails, _ := user_model.GetEmailAddresses(t.Context(), int64(1))
- assert.Len(t, emails, 3)
- assert.True(t, emails[0].IsActivated)
- assert.True(t, emails[0].IsPrimary)
- assert.False(t, emails[1].IsPrimary)
- assert.True(t, emails[2].IsActivated)
- assert.False(t, emails[2].IsPrimary)
-}
-
-func TestListEmails(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- // Must find all users and their emails
- opts := &user_model.SearchEmailOptions{
- ListOptions: db.ListOptions{
- PageSize: 10000,
- },
- }
- emails, count, err := user_model.SearchEmails(t.Context(), opts)
- assert.NoError(t, err)
- assert.Greater(t, count, int64(5))
-
- contains := func(match func(s *user_model.SearchEmailResult) bool) bool {
- return slices.ContainsFunc(emails, match)
- }
-
- assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 }))
- // 'org3' is an organization
- assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 3 }))
-
- // Must find no records
- opts = &user_model.SearchEmailOptions{Keyword: "NOTFOUND"}
- emails, count, err = user_model.SearchEmails(t.Context(), opts)
- assert.NoError(t, err)
- assert.Equal(t, int64(0), count)
-
- // Must find users 'user2', 'user28', etc.
- opts = &user_model.SearchEmailOptions{Keyword: "user2"}
- emails, count, err = user_model.SearchEmails(t.Context(), opts)
- assert.NoError(t, err)
- assert.NotEqual(t, int64(0), count)
- assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 2 }))
- assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 27 }))
-
- // Must find only primary addresses (i.e. from the `user` table)
- opts = &user_model.SearchEmailOptions{IsPrimary: optional.Some(true)}
- emails, _, err = user_model.SearchEmails(t.Context(), opts)
- assert.NoError(t, err)
- assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary }))
- assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary }))
-
- // Must find only inactive addresses (i.e. not validated)
- opts = &user_model.SearchEmailOptions{IsActivated: optional.Some(false)}
- emails, _, err = user_model.SearchEmails(t.Context(), opts)
- assert.NoError(t, err)
- assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated }))
- assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsActivated }))
-
- // Must find more than one page, but retrieve only one
- opts = &user_model.SearchEmailOptions{
- ListOptions: db.ListOptions{
- PageSize: 5,
- Page: 1,
- },
- }
- emails, count, err = user_model.SearchEmails(t.Context(), opts)
- assert.NoError(t, err)
- assert.Len(t, emails, 5)
- assert.Greater(t, count, int64(len(emails)))
+ })
}
func TestEmailAddressValidate(t *testing.T) {
diff --git a/models/user/openid_test.go b/models/user/openid_test.go
index b9a2572adb2..fcb2752b562 100644
--- a/models/user/openid_test.go
+++ b/models/user/openid_test.go
@@ -14,57 +14,57 @@ import (
"github.com/stretchr/testify/require"
)
-func TestGetUserOpenIDs(t *testing.T) {
+func TestUserOpenIDs(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- oids, err := user_model.GetUserOpenIDs(t.Context(), int64(1))
- if assert.NoError(t, err) && assert.Len(t, oids, 2) {
- assert.Equal(t, "https://user1.domain1.tld/", oids[0].URI)
+ t.Run("GetUserOpenIDs", func(t *testing.T) {
+ oids, err := user_model.GetUserOpenIDs(t.Context(), int64(1))
+ if assert.NoError(t, err) && assert.Len(t, oids, 2) {
+ assert.Equal(t, "https://user1.domain1.tld/", oids[0].URI)
+ assert.False(t, oids[0].Show)
+ assert.Equal(t, "http://user1.domain2.tld/", oids[1].URI)
+ assert.True(t, oids[1].Show)
+ }
+
+ oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
+ if assert.NoError(t, err) && assert.Len(t, oids, 1) {
+ assert.Equal(t, "https://domain1.tld/user2/", oids[0].URI)
+ assert.True(t, oids[0].Show)
+ }
+ })
+
+ t.Run("ToggleUserOpenIDVisibilityRequiresOwnership", func(t *testing.T) {
+ unauthorizedUser, err := user_model.GetUserByID(t.Context(), int64(2))
+ require.NoError(t, err)
+
+ err = user_model.ToggleUserOpenIDVisibility(t.Context(), int64(1), unauthorizedUser)
+ require.Error(t, err)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ })
+
+ t.Run("ToggleUserOpenIDVisibility", func(t *testing.T) {
+ user, err := user_model.GetUserByID(t.Context(), int64(2))
+ require.NoError(t, err)
+ oids, err := user_model.GetUserOpenIDs(t.Context(), int64(2))
+ require.NoError(t, err)
+ require.Len(t, oids, 1)
+ assert.True(t, oids[0].Show)
+
+ err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user)
+ require.NoError(t, err)
+
+ oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
+ require.NoError(t, err)
+ require.Len(t, oids, 1)
+
assert.False(t, oids[0].Show)
- assert.Equal(t, "http://user1.domain2.tld/", oids[1].URI)
- assert.True(t, oids[1].Show)
- }
+ err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user)
+ require.NoError(t, err)
- oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
- if assert.NoError(t, err) && assert.Len(t, oids, 1) {
- assert.Equal(t, "https://domain1.tld/user2/", oids[0].URI)
- assert.True(t, oids[0].Show)
- }
-}
-
-func TestToggleUserOpenIDVisibility(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- user, err := user_model.GetUserByID(t.Context(), int64(2))
- require.NoError(t, err)
- oids, err := user_model.GetUserOpenIDs(t.Context(), int64(2))
- require.NoError(t, err)
- require.Len(t, oids, 1)
- assert.True(t, oids[0].Show)
-
- err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user)
- require.NoError(t, err)
-
- oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
- require.NoError(t, err)
- require.Len(t, oids, 1)
-
- assert.False(t, oids[0].Show)
- err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user)
- require.NoError(t, err)
-
- oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
- require.NoError(t, err)
- if assert.Len(t, oids, 1) {
- assert.True(t, oids[0].Show)
- }
-}
-
-func TestToggleUserOpenIDVisibilityRequiresOwnership(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- unauthorizedUser, err := user_model.GetUserByID(t.Context(), int64(2))
- require.NoError(t, err)
-
- err = user_model.ToggleUserOpenIDVisibility(t.Context(), int64(1), unauthorizedUser)
- require.Error(t, err)
- assert.ErrorIs(t, err, util.ErrNotExist)
+ oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
+ require.NoError(t, err)
+ if assert.Len(t, oids, 1) {
+ assert.True(t, oids[0].Show)
+ }
+ })
}
diff --git a/models/user/user_test.go b/models/user/user_test.go
index 7d8f57ed0ce..a720d9425df 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -28,18 +28,13 @@ import (
)
func TestIsUsableUsername(t *testing.T) {
- assert.NoError(t, user_model.IsUsableUsername("a"))
- assert.NoError(t, user_model.IsUsableUsername("foo.wiki"))
- assert.NoError(t, user_model.IsUsableUsername("foo.git"))
+ for _, username := range []string{"a", "foo.wiki", "foo.git"} {
+ assert.NoError(t, user_model.IsUsableUsername(username))
+ }
- assert.Error(t, user_model.IsUsableUsername("a--b"))
- assert.Error(t, user_model.IsUsableUsername("-1_."))
- assert.Error(t, user_model.IsUsableUsername(".profile"))
- assert.Error(t, user_model.IsUsableUsername("-"))
- assert.Error(t, user_model.IsUsableUsername("🌞"))
- assert.Error(t, user_model.IsUsableUsername("the..repo"))
- assert.Error(t, user_model.IsUsableUsername("foo.RSS"))
- assert.Error(t, user_model.IsUsableUsername("foo.PnG"))
+ for _, username := range []string{"a--b", "-1_.", ".profile", "-", "🌞", "the..repo", "foo.RSS", "foo.PnG"} {
+ assert.Error(t, user_model.IsUsableUsername(username))
+ }
}
func TestOAuth2Application_LoadUser(t *testing.T) {
@@ -161,28 +156,17 @@ func TestSearchUsers(t *testing.T) {
}
// test orgs
- testOrgSuccess := func(opts user_model.SearchUserOptions, expectedOrgIDs []int64) {
- opts.Types = []user_model.UserType{user_model.UserTypeOrganization}
- testSuccess(opts, expectedOrgIDs)
+ for page, expectedOrgIDs := range [][]int64{{3, 6}, {7, 17}, {19, 25}, {26, 41}, {42}} {
+ testSuccess(user_model.SearchUserOptions{
+ Types: []user_model.UserType{user_model.UserTypeOrganization},
+ OrderBy: "id ASC",
+ ListOptions: db.ListOptions{Page: page + 1, PageSize: 2},
+ }, expectedOrgIDs)
}
-
- testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1, PageSize: 2}},
- []int64{3, 6})
-
- testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 2, PageSize: 2}},
- []int64{7, 17})
-
- testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 3, PageSize: 2}},
- []int64{19, 25})
-
- testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
- []int64{26, 41})
-
- testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
- []int64{42})
-
- testOrgSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 6, PageSize: 2}},
- []int64{})
+ testSuccess(user_model.SearchUserOptions{
+ Types: []user_model.UserType{user_model.UserTypeOrganization},
+ ListOptions: db.ListOptions{Page: 6, PageSize: 2},
+ }, []int64{})
// test users
testUserSuccess := func(opts user_model.SearchUserOptions, expectedUserIDs []int64) {
@@ -403,50 +387,34 @@ func TestGetMaileableUsersByIDs(t *testing.T) {
}
func TestNewUserRedirect(t *testing.T) {
- // redirect to a completely new name
- assert.NoError(t, unittest.PrepareTestDatabase())
+ for _, testCase := range []struct {
+ userID int64
+ newName string
+ }{
+ {1, "newusername"}, // redirect to a completely new name
+ {1, "olduser1"}, // redirect to previously used name
+ {2, "newusername"}, // redirect for a previously-unredirected user
+ } {
+ t.Run(fmt.Sprintf("%d/%s", testCase.userID, testCase.newName), func(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
- assert.NoError(t, user_model.NewUserRedirect(t.Context(), user.ID, user.Name, "newusername"))
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.userID})
+ assert.NoError(t, user_model.NewUserRedirect(t.Context(), user.ID, user.Name, testCase.newName))
- unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{
- LowerName: user.LowerName,
- RedirectUserID: user.ID,
- })
- unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{
- LowerName: "olduser1",
- RedirectUserID: user.ID,
- })
-}
-
-func TestNewUserRedirect2(t *testing.T) {
- // redirect to previously used name
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
- assert.NoError(t, user_model.NewUserRedirect(t.Context(), user.ID, user.Name, "olduser1"))
-
- unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{
- LowerName: user.LowerName,
- RedirectUserID: user.ID,
- })
- unittest.AssertNotExistsBean(t, &user_model.Redirect{
- LowerName: "olduser1",
- RedirectUserID: user.ID,
- })
-}
-
-func TestNewUserRedirect3(t *testing.T) {
- // redirect for a previously-unredirected user
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- assert.NoError(t, user_model.NewUserRedirect(t.Context(), user.ID, user.Name, "newusername"))
-
- unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{
- LowerName: user.LowerName,
- RedirectUserID: user.ID,
- })
+ unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{
+ LowerName: user.LowerName,
+ RedirectUserID: user.ID,
+ })
+ if testCase.userID == 1 {
+ redirect := &user_model.Redirect{LowerName: "olduser1", RedirectUserID: user.ID}
+ if testCase.newName == "olduser1" {
+ unittest.AssertNotExistsBean(t, redirect)
+ } else {
+ unittest.AssertExistsAndLoadBean(t, redirect)
+ }
+ }
+ })
+ }
}
func TestGetUserByOpenID(t *testing.T) {
@@ -546,13 +514,11 @@ func TestIsUserVisibleToViewer(t *testing.T) {
// limited user
test(user33, user33, true)
- test(user33, user4, true)
test(user33, user29, false)
test(user33, nil, false)
// private user
test(user31, user31, true)
- test(user31, user4, false)
test(user31, user20, true)
test(user31, user29, false)
test(user31, user33, true)
diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go
index ef8294565b4..0248ef56ede 100644
--- a/models/webhook/webhook_test.go
+++ b/models/webhook/webhook_test.go
@@ -322,120 +322,51 @@ func TestUpdateHookTask(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, hookTask)
}
-func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
+func TestCleanupHookTaskTable(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ url string
+ cleanupType HookTaskCleanupType
+ isDelivered bool
+ delivered func() timeutil.TimeStampNano
+ numberToKeep int
+ deleted bool
+ }{
+ {"PerWebhook_DeletesDelivered", "https://www.example.com/cleanup1", PerWebhook, true, timeutil.TimeStampNanoNow, 0, true},
+ {"PerWebhook_LeavesUndelivered", "https://www.example.com/cleanup2", PerWebhook, false, nil, 0, false},
+ {"PerWebhook_LeavesMostRecentTask", "https://www.example.com/cleanup3", PerWebhook, true, timeutil.TimeStampNanoNow, 1, false},
+ {"OlderThan_DeletesDelivered", "https://www.example.com/cleanup4", OlderThan, true, func() timeutil.TimeStampNano {
+ return timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano())
+ }, 0, true},
+ {"OlderThan_LeavesUndelivered", "https://www.example.com/cleanup5", OlderThan, false, nil, 0, false},
+ {"OlderThan_LeavesTaskEarlierThanAgeToDelete", "https://www.example.com/cleanup6", OlderThan, true, func() timeutil.TimeStampNano {
+ return timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano())
+ }, 0, false},
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ require.NoError(t, unittest.PrepareTestDatabase())
- hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup1", ContentType: ContentTypeJSON, Events: `{"push_only":true}`}
- require.NoError(t, db.Insert(t.Context(), hook))
- hookTask := &HookTask{
- HookID: hook.ID,
- IsDelivered: true,
- Delivered: timeutil.TimeStampNanoNow(),
- PayloadVersion: 2,
+ hook := &Webhook{RepoID: 3, URL: test.url, ContentType: ContentTypeJSON, Events: `{"push_only":true}`}
+ require.NoError(t, db.Insert(t.Context(), hook))
+ hookTask := &HookTask{
+ HookID: hook.ID,
+ IsDelivered: test.isDelivered,
+ PayloadVersion: 2,
+ }
+ if test.delivered != nil {
+ hookTask.Delivered = test.delivered()
+ }
+ unittest.AssertNotExistsBean(t, hookTask)
+ _, err := CreateHookTask(t.Context(), hookTask)
+ assert.NoError(t, err)
+ unittest.AssertExistsAndLoadBean(t, hookTask)
+
+ assert.NoError(t, CleanupHookTaskTable(t.Context(), test.cleanupType, 168*time.Hour, test.numberToKeep))
+ if test.deleted {
+ unittest.AssertNotExistsBean(t, hookTask)
+ } else {
+ unittest.AssertExistsAndLoadBean(t, hookTask)
+ }
+ })
}
- unittest.AssertNotExistsBean(t, hookTask)
- _, err := CreateHookTask(t.Context(), hookTask)
- assert.NoError(t, err)
- unittest.AssertExistsAndLoadBean(t, hookTask)
-
- assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 0))
- unittest.AssertNotExistsBean(t, hookTask)
-}
-
-func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
-
- hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup2", ContentType: ContentTypeJSON, Events: `{"push_only":true}`}
- require.NoError(t, db.Insert(t.Context(), hook))
- hookTask := &HookTask{
- HookID: hook.ID,
- IsDelivered: false,
- PayloadVersion: 2,
- }
- unittest.AssertNotExistsBean(t, hookTask)
- _, err := CreateHookTask(t.Context(), hookTask)
- assert.NoError(t, err)
- unittest.AssertExistsAndLoadBean(t, hookTask)
-
- assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 0))
- unittest.AssertExistsAndLoadBean(t, hookTask)
-}
-
-func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
-
- hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup3", ContentType: ContentTypeJSON, Events: `{"push_only":true}`}
- require.NoError(t, db.Insert(t.Context(), hook))
- hookTask := &HookTask{
- HookID: hook.ID,
- IsDelivered: true,
- Delivered: timeutil.TimeStampNanoNow(),
- PayloadVersion: 2,
- }
- unittest.AssertNotExistsBean(t, hookTask)
- _, err := CreateHookTask(t.Context(), hookTask)
- assert.NoError(t, err)
- unittest.AssertExistsAndLoadBean(t, hookTask)
-
- assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 1))
- unittest.AssertExistsAndLoadBean(t, hookTask)
-}
-
-func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
-
- hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup4", ContentType: ContentTypeJSON, Events: `{"push_only":true}`}
- require.NoError(t, db.Insert(t.Context(), hook))
- hookTask := &HookTask{
- HookID: hook.ID,
- IsDelivered: true,
- Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()),
- PayloadVersion: 2,
- }
- unittest.AssertNotExistsBean(t, hookTask)
- _, err := CreateHookTask(t.Context(), hookTask)
- assert.NoError(t, err)
- unittest.AssertExistsAndLoadBean(t, hookTask)
-
- assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0))
- unittest.AssertNotExistsBean(t, hookTask)
-}
-
-func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
-
- hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup5", ContentType: ContentTypeJSON, Events: `{"push_only":true}`}
- require.NoError(t, db.Insert(t.Context(), hook))
- hookTask := &HookTask{
- HookID: hook.ID,
- IsDelivered: false,
- PayloadVersion: 2,
- }
- unittest.AssertNotExistsBean(t, hookTask)
- _, err := CreateHookTask(t.Context(), hookTask)
- assert.NoError(t, err)
- unittest.AssertExistsAndLoadBean(t, hookTask)
-
- assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0))
- unittest.AssertExistsAndLoadBean(t, hookTask)
-}
-
-func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *testing.T) {
- require.NoError(t, unittest.PrepareTestDatabase())
-
- hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup6", ContentType: ContentTypeJSON, Events: `{"push_only":true}`}
- require.NoError(t, db.Insert(t.Context(), hook))
- hookTask := &HookTask{
- HookID: hook.ID,
- IsDelivered: true,
- Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()),
- PayloadVersion: 2,
- }
- unittest.AssertNotExistsBean(t, hookTask)
- _, err := CreateHookTask(t.Context(), hookTask)
- assert.NoError(t, err)
- unittest.AssertExistsAndLoadBean(t, hookTask)
-
- assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0))
- unittest.AssertExistsAndLoadBean(t, hookTask)
}
diff --git a/modules/actions/github_test.go b/modules/actions/github_test.go
index 26d05b8560c..b558bdd765b 100644
--- a/modules/actions/github_test.go
+++ b/modules/actions/github_test.go
@@ -77,12 +77,6 @@ func TestCanGithubEventMatch(t *testing.T) {
webhook_module.HookEventPullRequest,
true,
},
- {
- "pull_request_target cannot match",
- GithubEventPullRequest,
- webhook_module.HookEventPullRequestComment,
- false,
- },
// pull_request_review event
{
"pull_request_review matches",
diff --git a/modules/actions/jobparser/jobparser_test.go b/modules/actions/jobparser/jobparser_test.go
index 0a80219da6e..64e46b693a1 100644
--- a/modules/actions/jobparser/jobparser_test.go
+++ b/modules/actions/jobparser/jobparser_test.go
@@ -18,72 +18,21 @@ import (
)
func TestParse(t *testing.T) {
- tests := []struct {
- name string
- options []ParseOption
- wantErr bool
- }{
- {
- name: "multiple_jobs",
- options: nil,
- wantErr: false,
- },
- {
- name: "multiple_matrix",
- options: nil,
- wantErr: false,
- },
- {
- name: "has_needs",
- options: nil,
- wantErr: false,
- },
- {
- name: "has_with",
- options: nil,
- wantErr: false,
- },
- {
- name: "has_secrets",
- options: nil,
- wantErr: false,
- },
- {
- name: "empty_step",
- options: nil,
- wantErr: false,
- },
- {
- name: "job_name_with_matrix",
- options: nil,
- wantErr: false,
- },
- {
- name: "prefixed_newline",
- options: nil,
- wantErr: false,
- },
- {
- name: "continue_on_error_expr",
- options: nil,
- wantErr: false,
- },
- }
- invalidFileTests := []struct {
- name string
- }{
- {name: "null_job_implicit"},
- {name: "null_job_explicit"},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- content := ReadTestdata(t, tt.name+".in.yaml")
- want := ReadTestdata(t, tt.name+".out.yaml")
- got, err := Parse(content, append([]ParseOption{WithGitContext(&model.GithubContext{})}, tt.options...)...)
- if tt.wantErr {
- require.Error(t, err)
- }
+ for _, name := range []string{
+ "multiple_jobs",
+ "multiple_matrix",
+ "has_needs",
+ "has_with",
+ "has_secrets",
+ "empty_step",
+ "job_name_with_matrix",
+ "prefixed_newline",
+ "continue_on_error_expr",
+ } {
+ t.Run(name, func(t *testing.T) {
+ content := ReadTestdata(t, name+".in.yaml")
+ want := ReadTestdata(t, name+".out.yaml")
+ got, err := Parse(content, WithGitContext(&model.GithubContext{}))
require.NoError(t, err)
builder := &strings.Builder{}
@@ -102,9 +51,9 @@ func TestParse(t *testing.T) {
})
}
- for _, tt := range invalidFileTests {
- t.Run(tt.name, func(t *testing.T) {
- content := ReadTestdata(t, tt.name+".in.yaml")
+ for _, name := range []string{"null_job_implicit", "null_job_explicit"} {
+ t.Run(name, func(t *testing.T) {
+ content := ReadTestdata(t, name+".in.yaml")
require.NotPanics(t, func() {
_, err := Parse(content)
require.Error(t, err)
@@ -460,6 +409,8 @@ func TestExpandMatrixWithNeeds(t *testing.T) {
{"empty vector", "\n version: ${{ fromJson(needs.setup.outputs.empty) }}\n", `Matrix vector "version" does not contain any values`},
{"empty include", "\n include: ${{ fromJson(needs.setup.outputs.empty) }}\n", "Matrix must define at least one vector"},
{"whole matrix not a mapping", " ${{ fromJson(needs.setup.outputs.empty) }}\n", `Matrix "" is not a map of matrix keys to values`},
+ {"unresolved need", "\n v: ${{ fromJson(needs.missing.outputs.v) }}\n", "evaluate matrix"},
+ {"invalid strategy field", "\n os: [linux]\nmax-parallel: ${{ fromJSON('bad') }}\n", "evaluate strategy"},
} {
t.Run(tt.name+" errors", func(t *testing.T) {
_, err := expand(t, tt.matrix)
@@ -475,16 +426,6 @@ func TestExpandMatrixWithNeeds(t *testing.T) {
assert.Equal(t, 1, got[0].Strategy.JobTotal)
})
- t.Run("unresolved need errors", func(t *testing.T) {
- _, err := expand(t, "\n v: ${{ fromJson(needs.missing.outputs.v) }}\n")
- require.ErrorContains(t, err, "evaluate matrix")
- })
-
- t.Run("invalid strategy field errors", func(t *testing.T) {
- _, err := expand(t, "\n os: [linux]\nmax-parallel: ${{ fromJSON('bad') }}\n")
- require.ErrorContains(t, err, "evaluate strategy")
- })
-
// The combination count comes from a runtime output, so it must be rejected before one Job per
// combination is built rather than after.
t.Run("too many combinations errors", func(t *testing.T) {
diff --git a/modules/actions/jobparser/model_test.go b/modules/actions/jobparser/model_test.go
index af708b3cc95..3ed27a4426c 100644
--- a/modules/actions/jobparser/model_test.go
+++ b/modules/actions/jobparser/model_test.go
@@ -332,29 +332,17 @@ func TestSingleWorkflow_SetJob(t *testing.T) {
func TestGetContinueOnError(t *testing.T) {
tests := []struct {
- name string
- yaml string
- want bool
+ name string
+ continueOnError string
+ want bool
}{
- {
- name: "absent",
- yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n steps:\n - run: echo hi\n",
- want: false,
- },
- {
- name: "static true",
- yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n continue-on-error: true\n steps:\n - run: echo hi\n",
- want: true,
- },
- {
- name: "static false",
- yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n continue-on-error: false\n steps:\n - run: echo hi\n",
- want: false,
- },
+ {name: "absent", want: false},
+ {name: "static true", continueOnError: " continue-on-error: true\n", want: true},
+ {name: "static false", continueOnError: " continue-on-error: false\n", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got, err := Parse([]byte(tt.yaml))
+ got, err := Parse(fmt.Appendf(nil, "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n%s steps:\n - run: echo hi\n", tt.continueOnError))
require.NoError(t, err)
require.Len(t, got, 1)
_, job := got[0].Job()
diff --git a/modules/actions/workflowpattern/workflow_pattern_test.go b/modules/actions/workflowpattern/workflow_pattern_test.go
index 59a38326342..84cc96943f0 100644
--- a/modules/actions/workflowpattern/workflow_pattern_test.go
+++ b/modules/actions/workflowpattern/workflow_pattern_test.go
@@ -13,404 +13,214 @@ import (
func TestMatchPattern(t *testing.T) {
kases := []struct {
- inputs []string
+ inputs [][]string
patterns []string
skipResult bool
filterResult bool
}{
{
patterns: []string{"*"},
- inputs: []string{"path/with/slash"},
+ inputs: [][]string{{"path/with/slash"}},
skipResult: true,
filterResult: false,
},
{
patterns: []string{"path/a", "path/b", "path/c"},
- inputs: []string{"meta", "path/b", "otherfile"},
+ inputs: [][]string{{"meta", "path/b", "otherfile"}},
skipResult: false,
filterResult: false,
},
{
patterns: []string{"path/a", "path/b", "path/c"},
- inputs: []string{"path/b"},
+ inputs: [][]string{{"path/b"}, {"path/c", "path/b"}, {"path/c", "path/b", "path/a"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"path/a", "path/b", "path/c"},
- inputs: []string{"path/c", "path/b"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"path/a", "path/b", "path/c"},
- inputs: []string{"path/c", "path/b", "path/a"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"path/a", "path/b", "path/c"},
- inputs: []string{"path/c", "path/b", "path/d", "path/a"},
+ inputs: [][]string{{"path/c", "path/b", "path/d", "path/a"}},
skipResult: false,
filterResult: false,
},
{
patterns: []string{},
- inputs: []string{},
+ inputs: [][]string{{}},
skipResult: false,
filterResult: false,
},
{
patterns: []string{"\\!file"},
- inputs: []string{"!file"},
+ inputs: [][]string{{"!file"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"escape\\\\backslash"},
- inputs: []string{"escape\\backslash"},
+ inputs: [][]string{{"escape\\backslash"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{".yml"},
- inputs: []string{"fyml"},
+ inputs: [][]string{{"fyml"}},
skipResult: true,
filterResult: false,
},
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#patterns-to-match-branches-and-tags
{
patterns: []string{"feature/*"},
- inputs: []string{"feature/my-branch"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"feature/*"},
- inputs: []string{"feature/your-branch"},
+ inputs: [][]string{{"feature/my-branch"}, {"feature/your-branch"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"feature/**"},
- inputs: []string{"feature/beta-a/my-branch"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"feature/**"},
- inputs: []string{"feature/beta-a/my-branch"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"feature/**"},
- inputs: []string{"feature/mona/the/octocat"},
+ inputs: [][]string{{"feature/beta-a/my-branch"}, {"feature/mona/the/octocat"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"main", "releases/mona-the-octocat"},
- inputs: []string{"main"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"main", "releases/mona-the-octocat"},
- inputs: []string{"releases/mona-the-octocat"},
+ inputs: [][]string{{"main"}, {"releases/mona-the-octocat"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"*"},
- inputs: []string{"main"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"*"},
- inputs: []string{"releases"},
+ inputs: [][]string{{"main"}, {"releases"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"**"},
- inputs: []string{"all/the/branches"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**"},
- inputs: []string{"every/tag"},
+ inputs: [][]string{{"all/the/branches"}, {"every/tag"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"*feature"},
- inputs: []string{"mona-feature"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"*feature"},
- inputs: []string{"feature"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"*feature"},
- inputs: []string{"ver-10-feature"},
+ inputs: [][]string{{"mona-feature"}, {"feature"}, {"ver-10-feature"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"v2*"},
- inputs: []string{"v2"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"v2*"},
- inputs: []string{"v2.0"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"v2*"},
- inputs: []string{"v2.9"},
+ inputs: [][]string{{"v2"}, {"v2.0"}, {"v2.9"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"v[12].[0-9]+.[0-9]+"},
- inputs: []string{"v1.10.1"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"v[12].[0-9]+.[0-9]+"},
- inputs: []string{"v2.0.0"},
+ inputs: [][]string{{"v1.10.1"}, {"v2.0.0"}},
skipResult: false,
filterResult: true,
},
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#patterns-to-match-file-paths
{
patterns: []string{"*"},
- inputs: []string{"README.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"*"},
- inputs: []string{"server.rb"},
+ inputs: [][]string{{"README.md"}, {"server.rb"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"*.jsx?"},
- inputs: []string{"page.js"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"*.jsx?"},
- inputs: []string{"page.jsx"},
+ inputs: [][]string{{"page.js"}, {"page.jsx"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"**"},
- inputs: []string{"all/the/files.md"},
+ inputs: [][]string{{"all/the/files.md"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"*.js"},
- inputs: []string{"app.js"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"*.js"},
- inputs: []string{"index.js"},
+ inputs: [][]string{{"app.js"}, {"index.js"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"**.js"},
- inputs: []string{"index.js"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**.js"},
- inputs: []string{"js/index.js"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**.js"},
- inputs: []string{"src/js/app.js"},
+ inputs: [][]string{{"index.js"}, {"js/index.js"}, {"src/js/app.js"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"docs/*"},
- inputs: []string{"docs/README.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"docs/*"},
- inputs: []string{"docs/file.txt"},
+ inputs: [][]string{{"docs/README.md"}, {"docs/file.txt"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"docs/**"},
- inputs: []string{"docs/README.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"docs/**"},
- inputs: []string{"docs/mona/octocat.txt"},
+ inputs: [][]string{{"docs/README.md"}, {"docs/mona/octocat.txt"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"docs/**/*.md"},
- inputs: []string{"docs/README.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"docs/**/*.md"},
- inputs: []string{"docs/mona/hello-world.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"docs/**/*.md"},
- inputs: []string{"docs/a/markdown/file.md"},
+ inputs: [][]string{{"docs/README.md"}, {"docs/mona/hello-world.md"}, {"docs/a/markdown/file.md"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"**/docs/**"},
- inputs: []string{"docs/hello.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**/docs/**"},
- inputs: []string{"dir/docs/my-file.txt"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**/docs/**"},
- inputs: []string{"space/docs/plan/space.doc"},
+ inputs: [][]string{{"docs/hello.md"}, {"dir/docs/my-file.txt"}, {"space/docs/plan/space.doc"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"**/README.md"},
- inputs: []string{"README.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**/README.md"},
- inputs: []string{"js/README.md"},
+ inputs: [][]string{{"README.md"}, {"js/README.md"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"**/*src/**"},
- inputs: []string{"a/src/app.js"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**/*src/**"},
- inputs: []string{"my-src/code/js/app.js"},
+ inputs: [][]string{{"a/src/app.js"}, {"my-src/code/js/app.js"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"**/*-post.md"},
- inputs: []string{"my-post.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**/*-post.md"},
- inputs: []string{"path/their-post.md"},
+ inputs: [][]string{{"my-post.md"}, {"path/their-post.md"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"**/migrate-*.sql"},
- inputs: []string{"migrate-10909.sql"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**/migrate-*.sql"},
- inputs: []string{"db/migrate-v1.0.sql"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"**/migrate-*.sql"},
- inputs: []string{"db/sept/migrate-v1.sql"},
+ inputs: [][]string{{"migrate-10909.sql"}, {"db/migrate-v1.0.sql"}, {"db/sept/migrate-v1.sql"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"*.md", "!README.md"},
- inputs: []string{"hello.md"},
+ inputs: [][]string{{"hello.md"}},
skipResult: false,
filterResult: true,
},
{
patterns: []string{"*.md", "!README.md"},
- inputs: []string{"README.md"},
- skipResult: true,
- filterResult: true,
- },
- {
- patterns: []string{"*.md", "!README.md"},
- inputs: []string{"docs/hello.md"},
+ inputs: [][]string{{"README.md"}, {"docs/hello.md"}},
skipResult: true,
filterResult: true,
},
{
patterns: []string{"*.md", "!README.md", "README*"},
- inputs: []string{"hello.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"*.md", "!README.md", "README*"},
- inputs: []string{"README.md"},
- skipResult: false,
- filterResult: true,
- },
- {
- patterns: []string{"*.md", "!README.md", "README*"},
- inputs: []string{"README.doc"},
+ inputs: [][]string{{"hello.md"}, {"README.md"}, {"README.doc"}},
skipResult: false,
filterResult: true,
},
}
for _, kase := range kases {
- msg := fmt.Sprintf("patterns=%s, input=%s", strings.Join(kase.patterns, ","), strings.Join(kase.inputs, ","))
- patterns, err := CompilePatterns(kase.patterns...)
- assert.NoError(t, err, "compile error: %s", msg)
- assert.Equal(t, kase.skipResult, Skip(patterns, kase.inputs), "unexpected skip result: %s", msg)
- assert.Equal(t, kase.filterResult, Filter(patterns, kase.inputs), "unexpected filter result: %s", msg)
+ for _, inputs := range kase.inputs {
+ msg := fmt.Sprintf("patterns=%s, input=%s", strings.Join(kase.patterns, ","), strings.Join(inputs, ","))
+ patterns, err := CompilePatterns(kase.patterns...)
+ assert.NoError(t, err, "compile error: %s", msg)
+ assert.Equal(t, kase.skipResult, Skip(patterns, inputs), "unexpected skip result: %s", msg)
+ assert.Equal(t, kase.filterResult, Filter(patterns, inputs), "unexpected filter result: %s", msg)
+ }
}
}
diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go
index 8672d3205ac..a2536f9affd 100644
--- a/modules/actions/workflows_test.go
+++ b/modules/actions/workflows_test.go
@@ -281,6 +281,24 @@ func TestDetectMatched(t *testing.T) {
}
func TestMatchIssuesEvent(t *testing.T) {
+ labelChangesPayload := func() *api.IssuePayload {
+ return &api.IssuePayload{
+ Action: api.HookIssueLabelUpdated,
+ Issue: &api.Issue{
+ Labels: []*api.Label{
+ {ID: 789, Name: "new-label"},
+ },
+ },
+ Changes: &api.ChangesPayload{
+ AddedLabels: []*api.Label{
+ {ID: 789, Name: "new-label"},
+ },
+ RemovedLabels: []*api.Label{
+ {ID: 123, Name: "deleted-label"},
+ },
+ },
+ }
+ }
testCases := []struct {
desc string
payload *api.IssuePayload
@@ -358,67 +376,22 @@ func TestMatchIssuesEvent(t *testing.T) {
eventType: "unlabeled",
},
{
- desc: "Both adding and removing labels should trigger labeled event",
- payload: &api.IssuePayload{
- Action: api.HookIssueLabelUpdated,
- Issue: &api.Issue{
- Labels: []*api.Label{
- {ID: 789, Name: "new-label"},
- },
- },
- Changes: &api.ChangesPayload{
- AddedLabels: []*api.Label{
- {ID: 789, Name: "new-label"},
- },
- RemovedLabels: []*api.Label{
- {ID: 123, Name: "deleted-label"},
- },
- },
- },
+ desc: "Both adding and removing labels should trigger labeled event",
+ payload: labelChangesPayload(),
yamlOn: "on:\n issues:\n types: [labeled]",
expected: true,
eventType: "labeled",
},
{
- desc: "Both adding and removing labels should trigger unlabeled event",
- payload: &api.IssuePayload{
- Action: api.HookIssueLabelUpdated,
- Issue: &api.Issue{
- Labels: []*api.Label{
- {ID: 789, Name: "new-label"},
- },
- },
- Changes: &api.ChangesPayload{
- AddedLabels: []*api.Label{
- {ID: 789, Name: "new-label"},
- },
- RemovedLabels: []*api.Label{
- {ID: 123, Name: "deleted-label"},
- },
- },
- },
+ desc: "Both adding and removing labels should trigger unlabeled event",
+ payload: labelChangesPayload(),
yamlOn: "on:\n issues:\n types: [unlabeled]",
expected: true,
eventType: "unlabeled",
},
{
- desc: "Both adding and removing labels should trigger both events",
- payload: &api.IssuePayload{
- Action: api.HookIssueLabelUpdated,
- Issue: &api.Issue{
- Labels: []*api.Label{
- {ID: 789, Name: "new-label"},
- },
- },
- Changes: &api.ChangesPayload{
- AddedLabels: []*api.Label{
- {ID: 789, Name: "new-label"},
- },
- RemovedLabels: []*api.Label{
- {ID: 123, Name: "deleted-label"},
- },
- },
- },
+ desc: "Both adding and removing labels should trigger both events",
+ payload: labelChangesPayload(),
yamlOn: "on:\n issues:\n types: [labeled, unlabeled]",
expected: true,
eventType: "multiple",
diff --git a/modules/assetfs/layered_test.go b/modules/assetfs/layered_test.go
index b549815ea5c..c311c6116d7 100644
--- a/modules/assetfs/layered_test.go
+++ b/modules/assetfs/layered_test.go
@@ -74,34 +74,22 @@ func TestLayered(t *testing.T) {
assertRead("", fs.ErrNotExist, "no-such")
- files, err := assets.ListFiles(".", true)
- assert.NoError(t, err)
- assert.Equal(t, []string{"f1", "f2", "fa"}, files)
-
- files, err = assets.ListFiles(".", false)
- assert.NoError(t, err)
- assert.Equal(t, []string{"d1", "d2", "da"}, files)
-
- files, err = assets.ListFiles(".")
- assert.NoError(t, err)
- assert.Equal(t, []string{"d1", "d2", "da", "f1", "f2", "fa"}, files)
-
- files, err = assets.ListAllFiles(".", true)
- assert.NoError(t, err)
- assert.Equal(t, []string{"d1/f", "d2/f", "da/f", "f1", "f2", "fa"}, files)
-
- files, err = assets.ListAllFiles(".", false)
- assert.NoError(t, err)
- assert.Equal(t, []string{"d1", "d2", "da", "da/sub1", "da/sub2"}, files)
-
- files, err = assets.ListAllFiles(".")
- assert.NoError(t, err)
- assert.Equal(t, []string{
+ assertList := func(listFiles func(string, ...bool) ([]string, error), expected []string, fileMode ...bool) {
+ files, err := listFiles(".", fileMode...)
+ assert.NoError(t, err)
+ assert.Equal(t, expected, files)
+ }
+ assertList(assets.ListFiles, []string{"f1", "f2", "fa"}, true)
+ assertList(assets.ListFiles, []string{"d1", "d2", "da"}, false)
+ assertList(assets.ListFiles, []string{"d1", "d2", "da", "f1", "f2", "fa"})
+ assertList(assets.ListAllFiles, []string{"d1/f", "d2/f", "da/f", "f1", "f2", "fa"}, true)
+ assertList(assets.ListAllFiles, []string{"d1", "d2", "da", "da/sub1", "da/sub2"}, false)
+ assertList(assets.ListAllFiles, []string{
"d1", "d1/f",
"d2", "d2/f",
"da", "da/f", "da/sub1", "da/sub2",
"f1", "f2", "fa",
- }, files)
+ })
assert.Empty(t, assets.GetFileLayerName("no-such"))
assert.Equal(t, "l1", assets.GetFileLayerName("f1"))
diff --git a/modules/auth/password/password_test.go b/modules/auth/password/password_test.go
index 0fea593c859..36c02e7781f 100644
--- a/modules/auth/password/password_test.go
+++ b/modules/auth/password/password_test.go
@@ -48,7 +48,7 @@ func TestComplexity_Generate(t *testing.T) {
const maxCount = 50
const pwdLen = 50
- test := func(t *testing.T, modes []string) {
+ for _, modes := range [][]string{{"lower"}, {"upper"}, {"lower", "upper", "spec"}, {"off"}, {""}} {
testComplextity(modes)
for range maxCount {
pwd, err := Generate(pwdLen)
@@ -58,12 +58,6 @@ func TestComplexity_Generate(t *testing.T) {
}
}
- test(t, []string{"lower"})
- test(t, []string{"upper"})
- test(t, []string{"lower", "upper", "spec"})
- test(t, []string{"off"})
- test(t, []string{""})
-
// Remove settings for other tests
testComplextity([]string{"off"})
}
diff --git a/modules/auth/password/pwn/pwn_test.go b/modules/auth/password/pwn/pwn_test.go
index 3462a397a58..6600d10813c 100644
--- a/modules/auth/password/pwn/pwn_test.go
+++ b/modules/auth/password/pwn/pwn_test.go
@@ -47,25 +47,21 @@ func TestPassword(t *testing.T) {
assert.ErrorContains(t, err, "password cannot be empty")
assert.EqualValues(t, -1, count)
- count, err = client.CheckPassword(ctx, "pwned", false)
- assert.NoError(t, err)
- assert.EqualValues(t, 1, count)
-
- count, err = client.CheckPassword(ctx, "notpwned", false)
- assert.NoError(t, err)
- assert.EqualValues(t, 0, count)
-
- count, err = client.CheckPassword(ctx, "paddedpwned", true)
- assert.NoError(t, err)
- assert.EqualValues(t, 1, count)
-
- count, err = client.CheckPassword(ctx, "paddednotpwned", true)
- assert.NoError(t, err)
- assert.EqualValues(t, 0, count)
-
- count, err = client.CheckPassword(ctx, "paddednotpwnedzero", true)
- assert.NoError(t, err)
- assert.EqualValues(t, 0, count)
+ for _, test := range []struct {
+ password string
+ padding bool
+ count int
+ }{
+ {"pwned", false, 1},
+ {"notpwned", false, 0},
+ {"paddedpwned", true, 1},
+ {"paddednotpwned", true, 0},
+ {"paddednotpwnedzero", true, 0},
+ } {
+ count, err = client.CheckPassword(ctx, test.password, test.padding)
+ assert.NoError(t, err)
+ assert.EqualValues(t, test.count, count)
+ }
count, err = client.CheckPassword(ctx, "ratelimited", false)
assert.Error(t, err)
diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go
index f2e2879e0ab..6231f075d8f 100644
--- a/modules/avatar/avatar_test.go
+++ b/modules/avatar/avatar_test.go
@@ -16,26 +16,19 @@ import (
"github.com/stretchr/testify/assert"
)
-func Test_ProcessAvatarPNG(t *testing.T) {
- setting.Avatar.MaxWidth = 4096
- setting.Avatar.MaxHeight = 4096
+func Test_ProcessAvatarFormats(t *testing.T) {
+ for _, format := range []string{"png", "jpeg"} {
+ t.Run(format, func(t *testing.T) {
+ setting.Avatar.MaxWidth = 4096
+ setting.Avatar.MaxHeight = 4096
- data, err := os.ReadFile("testdata/avatar.png")
- assert.NoError(t, err)
+ data, err := os.ReadFile("testdata/avatar." + format)
+ assert.NoError(t, err)
- _, err = processAvatarImage(data, 262144)
- assert.NoError(t, err)
-}
-
-func Test_ProcessAvatarJPEG(t *testing.T) {
- setting.Avatar.MaxWidth = 4096
- setting.Avatar.MaxHeight = 4096
-
- data, err := os.ReadFile("testdata/avatar.jpeg")
- assert.NoError(t, err)
-
- _, err = processAvatarImage(data, 262144)
- assert.NoError(t, err)
+ _, err = processAvatarImage(data, 262144)
+ assert.NoError(t, err)
+ })
+ }
}
func Test_ProcessAvatarInvalidData(t *testing.T) {
diff --git a/modules/charset/charset_test.go b/modules/charset/charset_test.go
index 0731b0c0e15..ec31c4c80e8 100644
--- a/modules/charset/charset_test.go
+++ b/modules/charset/charset_test.go
@@ -87,90 +87,64 @@ func TestToUTF8(t *testing.T) {
}
func TestToUTF8WithFallback(t *testing.T) {
- // "ABC"
- res := ToUTF8WithFallback([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
- assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
+ for _, converter := range []struct {
+ name string
+ convert func([]byte) []byte
+ }{
+ {"fallback", func(content []byte) []byte { return ToUTF8WithFallback(content, ConvertOpts{}) }},
+ {"drop errors", ToUTF8DropErrors},
+ } {
+ t.Run(converter.name, func(t *testing.T) {
+ // "ABC"
+ res := converter.convert([]byte{0x41, 0x42, 0x43})
+ assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
- // "áéíóú"
- res = ToUTF8WithFallback([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
- assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
+ // "áéíóú"
+ res = converter.convert([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})
+ assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
- // UTF8 BOM + "áéíóú"
- res = ToUTF8WithFallback([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
- assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
+ // UTF8 BOM + "áéíóú"
+ res = converter.convert([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})
+ assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
- // "Hola, así cómo ños"
- res = ToUTF8WithFallback([]byte{
- 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
- 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73,
- }, ConvertOpts{})
- assert.Equal(t, []byte{
- 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63,
- 0xC3, 0xB3, 0x6D, 0x6F, 0x20, 0xC3, 0xB1, 0x6F, 0x73,
- }, res)
+ // "Hola, así cómo ños"
+ res = converter.convert([]byte{
+ 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
+ 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73,
+ })
+ if converter.name == "fallback" {
+ assert.Equal(t, []byte{
+ 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63,
+ 0xC3, 0xB3, 0x6D, 0x6F, 0x20, 0xC3, 0xB1, 0x6F, 0x73,
+ }, res)
+ } else {
+ assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73}, res[:8])
+ assert.Equal(t, []byte{0x73}, res[len(res)-1:])
+ }
- // "Hola, así cómo "
- minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20}
+ // "Hola, así cómo "
+ minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20}
- res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}, ConvertOpts{})
- // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
- assert.Equal(t, minmatch, res[0:len(minmatch)])
+ res = converter.convert([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73})
+ // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
+ assert.Equal(t, minmatch, res[0:len(minmatch)])
- res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}, ConvertOpts{})
- // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
- assert.Equal(t, minmatch, res[0:len(minmatch)])
+ res = converter.convert([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73})
+ // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
+ assert.Equal(t, minmatch, res[0:len(minmatch)])
- // Japanese (Shift-JIS)
- // "日属秘ぞしちゅ。"
- res = ToUTF8WithFallback([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}, ConvertOpts{})
- assert.Equal(t, []byte{
- 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
- 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
- }, res)
+ // Japanese (Shift-JIS)
+ // "日属秘ぞしちゅ。"
+ res = converter.convert([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42})
+ assert.Equal(t, []byte{
+ 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
+ 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
+ }, res)
- res = ToUTF8WithFallback([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{})
- assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
-}
-
-func TestToUTF8DropErrors(t *testing.T) {
- // "ABC"
- res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43})
- assert.Equal(t, []byte{0x41, 0x42, 0x43}, res)
-
- // "áéíóú"
- res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})
- assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
-
- // UTF8 BOM + "áéíóú"
- res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})
- assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
-
- // "Hola, así cómo ños"
- res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73})
- assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73}, res[:8])
- assert.Equal(t, []byte{0x73}, res[len(res)-1:])
-
- // "Hola, así cómo "
- minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20}
-
- res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73})
- // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
- assert.Equal(t, minmatch, res[0:len(minmatch)])
-
- res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73})
- // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those
- assert.Equal(t, minmatch, res[0:len(minmatch)])
-
- // Japanese (Shift-JIS)
- // "日属秘ぞしちゅ。"
- res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42})
- assert.Equal(t, []byte{
- 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
- 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
- }, res)
-
- res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00})
- assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
+ res = converter.convert([]byte{0x00, 0x00, 0x00, 0x00})
+ assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
+ })
+ }
assert.Equal(t, "caf ", string(ToUTF8DropErrors([]byte("caf\xe9"))))
}
diff --git a/modules/commitstatus/commit_status_test.go b/modules/commitstatus/commit_status_test.go
index dc8f9ce1a5e..a5fffd914f3 100644
--- a/modules/commitstatus/commit_status_test.go
+++ b/modules/commitstatus/commit_status_test.go
@@ -173,11 +173,6 @@ func TestCombine(t *testing.T) {
states: CommitStatusStates{CommitStatusSuccess, CommitStatusError, CommitStatusFailure, CommitStatusWarning},
expected: CommitStatusFailure,
},
- {
- name: "mixed states",
- states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusError, CommitStatusWarning},
- expected: CommitStatusFailure,
- },
{
name: "mixed states with all success",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusPending, CommitStatusWarning},
diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go
index d217d57e537..899156ea2a0 100644
--- a/modules/csv/csv_test.go
+++ b/modules/csv/csv_test.go
@@ -307,6 +307,7 @@ func TestGuessDelimiter(t *testing.T) {
cases := []struct {
csv string
expectedDelimiter rune
+ checkQuotes bool
}{
// case 0 - single cell, comma delmited
{
@@ -373,12 +374,14 @@ quoted,
quoted,
text," d`,
expectedDelimiter: '\t',
+ checkQuotes: true,
},
// case 10 - semicolon delmited with quotes and semicolon in value
{
csv: `col1;col2
"this has a literal "" in the text";"and an ; in the text"`,
expectedDelimiter: ';',
+ checkQuotes: true,
},
// case 11 - pipe delimited with quotes
{
@@ -389,6 +392,7 @@ World"|123
|
f" | 4.56 | 789`,
expectedDelimiter: '|',
+ checkQuotes: true,
},
// case 12 - a tab delimited 6 column CSV, but the values are not quoted and have lots of commas.
// In the previous bestScore algorithm, this would have picked comma as the delimiter, but now it should guess tab
@@ -455,6 +459,10 @@ jkl`,
for n, c := range cases {
delimiter := guessDelimiter([]byte(decodeSlashes(t, c.csv)))
assert.Equal(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
+ if c.checkQuotes {
+ delimiter = guessFromBeforeAfterQuotes([]byte(decodeSlashes(t, c.csv)))
+ assert.Equal(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
+ }
}
}
@@ -463,39 +471,6 @@ func TestGuessFromBeforeAfterQuotes(t *testing.T) {
csv string
expectedDelimiter rune
}{
- // case 0 - tab delimited with new lines in values, commas in values
- {
- csv: `1 "some,""more
-""
- quoted,
-text," a
-2 "some,
-quoted,\t
- text," b
-3 "some,
-quoted,
- text" c
-4 "some,
-quoted,
-text," d`,
- expectedDelimiter: '\t',
- },
- // case 1 - semicolon delmited with quotes and semicolon in value
- {
- csv: `col1;col2
-"this has a literal "" in the text";"and an ; in the text"`,
- expectedDelimiter: ';',
- },
- // case 2 - pipe delimited with quotes
- {
- csv: `Col1 | Col2 | Col3
-abc | "Hello
-World"|123
-"de
-|
-f" | 4.56 | 789`,
- expectedDelimiter: '|',
- },
// case 3 - a complicated quoted CSV that is semicolon delmiited
{
csv: `he; she
diff --git a/modules/fileicon/render_test.go b/modules/fileicon/render_test.go
index ac03fc49286..c1c5edd6eb7 100644
--- a/modules/fileicon/render_test.go
+++ b/modules/fileicon/render_test.go
@@ -16,60 +16,36 @@ import (
func TestRenderEntryIconHTML_WithDifferentThemes(t *testing.T) {
// Test that folder icons use the folder theme
- t.Run("FolderUsesBasicTheme", func(t *testing.T) {
- defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
- defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")()
-
- folderEntry := &fileicon.EntryInfo{
- BaseName: "testfolder",
- EntryMode: git.EntryModeTree,
- }
-
- html := fileicon.RenderEntryIconHTML(nil, folderEntry)
+ for _, testCase := range []struct {
+ name, folderTheme, expected string
+ entry fileicon.EntryInfo
+ }{
// Basic theme renders octicon classes
- assert.Contains(t, string(html), "octicon-file-directory-fill")
- })
-
- t.Run("FileUsesMaterialTheme", func(t *testing.T) {
- defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
- defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")()
-
- fileEntry := &fileicon.EntryInfo{
- BaseName: "test.js",
- EntryMode: git.EntryModeBlob,
- }
-
- html := fileicon.RenderEntryIconHTML(nil, fileEntry)
+ {
+ "FolderUsesBasicTheme", "basic", "octicon-file-directory-fill",
+ fileicon.EntryInfo{BaseName: "testfolder", EntryMode: git.EntryModeTree},
+ },
// Material theme for files renders material icons
- assert.Contains(t, string(html), "svg-mfi-")
- })
-
- t.Run("SymlinkToFolderUsesBasicTheme", func(t *testing.T) {
- defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
- defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")()
-
- symlinkEntry := &fileicon.EntryInfo{
- BaseName: "link",
- EntryMode: git.EntryModeSymlink,
- SymlinkToMode: git.EntryModeTree,
- }
-
- html := fileicon.RenderEntryIconHTML(nil, symlinkEntry)
+ {
+ "FileUsesMaterialTheme", "basic", "svg-mfi-",
+ fileicon.EntryInfo{BaseName: "test.js", EntryMode: git.EntryModeBlob},
+ },
// Symlinks to folders should use folder theme
- assert.Contains(t, string(html), "octicon-file-directory-symlink")
- })
-
- t.Run("BothMaterialTheme", func(t *testing.T) {
- defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
- defer test.MockVariableValue(&setting.UI.FolderIconTheme, "material")()
-
- folderEntry := &fileicon.EntryInfo{
- BaseName: "testfolder",
- EntryMode: git.EntryModeTree,
- }
-
- html := fileicon.RenderEntryIconHTML(nil, folderEntry)
+ {
+ "SymlinkToFolderUsesBasicTheme", "basic", "octicon-file-directory-symlink",
+ fileicon.EntryInfo{BaseName: "link", EntryMode: git.EntryModeSymlink, SymlinkToMode: git.EntryModeTree},
+ },
// Material theme for folders renders material folder icons
- assert.Contains(t, string(html), "svg-mfi-")
- })
+ {
+ "BothMaterialTheme", "material", "svg-mfi-",
+ fileicon.EntryInfo{BaseName: "testfolder", EntryMode: git.EntryModeTree},
+ },
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
+ defer test.MockVariableValue(&setting.UI.FolderIconTheme, testCase.folderTheme)()
+
+ assert.Contains(t, string(fileicon.RenderEntryIconHTML(nil, &testCase.entry)), testCase.expected)
+ })
+ }
}
diff --git a/modules/git/attribute/batch_test.go b/modules/git/attribute/batch_test.go
index 8336970e344..89d1525bf87 100644
--- a/modules/git/attribute/batch_test.go
+++ b/modules/git/attribute/batch_test.go
@@ -23,50 +23,31 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00"
- n, err := wr.Write([]byte(testStr))
-
- assert.Len(t, testStr, n)
- assert.NoError(t, err)
- select {
- case attr := <-wr.ReadAttribute():
- assert.Equal(t, ".gitignore\"\n", attr.Filename)
- assert.Equal(t, LinguistVendored, attr.Attribute)
- assert.Equal(t, "unspecified", attr.Value)
- case <-time.After(100 * time.Millisecond):
- assert.FailNow(t, "took too long to read an attribute from the list")
- }
- // Write a second attribute again
- n, err = wr.Write([]byte(testStr))
-
- assert.Len(t, testStr, n)
- assert.NoError(t, err)
-
- select {
- case attr := <-wr.ReadAttribute():
- assert.Equal(t, ".gitignore\"\n", attr.Filename)
- assert.Equal(t, LinguistVendored, attr.Attribute)
- assert.Equal(t, "unspecified", attr.Value)
- case <-time.After(100 * time.Millisecond):
- assert.FailNow(t, "took too long to read an attribute from the list")
+ for range 2 {
+ n, err := wr.Write([]byte(testStr))
+ assert.Len(t, testStr, n)
+ assert.NoError(t, err)
+ select {
+ case attr := <-wr.ReadAttribute():
+ assert.Equal(t, ".gitignore\"\n", attr.Filename)
+ assert.Equal(t, LinguistVendored, attr.Attribute)
+ assert.Equal(t, "unspecified", attr.Value)
+ case <-time.After(100 * time.Millisecond):
+ assert.FailNow(t, "took too long to read an attribute from the list")
+ }
}
// Write a partial attribute
- _, err = wr.Write([]byte("incomplete-file"))
+ _, err := wr.Write([]byte("incomplete-file"))
assert.NoError(t, err)
- _, err = wr.Write([]byte("name\x00"))
- assert.NoError(t, err)
-
- select {
- case <-wr.ReadAttribute():
- assert.FailNow(t, "There should not be an attribute ready to read")
- case <-time.After(100 * time.Millisecond):
- }
- _, err = wr.Write([]byte("attribute\x00"))
- assert.NoError(t, err)
- select {
- case <-wr.ReadAttribute():
- assert.FailNow(t, "There should not be an attribute ready to read")
- case <-time.After(100 * time.Millisecond):
+ for _, part := range []string{"name\x00", "attribute\x00"} {
+ _, err = wr.Write([]byte(part))
+ assert.NoError(t, err)
+ select {
+ case <-wr.ReadAttribute():
+ assert.FailNow(t, "There should not be an attribute ready to read")
+ case <-time.After(100 * time.Millisecond):
+ }
}
_, err = wr.Write([]byte("value\x00"))
@@ -79,27 +60,15 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
_, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00"))
assert.NoError(t, err)
- attr = <-wr.ReadAttribute()
- assert.NoError(t, err)
- assert.Equal(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: LinguistVendored,
- Value: "set",
- }, attr)
- attr = <-wr.ReadAttribute()
- assert.NoError(t, err)
- assert.Equal(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: LinguistGenerated,
- Value: "unspecified",
- }, attr)
- attr = <-wr.ReadAttribute()
- assert.NoError(t, err)
- assert.Equal(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: LinguistLanguage,
- Value: "unspecified",
- }, attr)
+ for _, expected := range []attributeTriple{
+ {"shouldbe.vendor", LinguistVendored, "set"},
+ {"shouldbe.vendor", LinguistGenerated, "unspecified"},
+ {"shouldbe.vendor", LinguistLanguage, "unspecified"},
+ } {
+ attr = <-wr.ReadAttribute()
+ assert.NoError(t, err)
+ assert.Equal(t, expected, attr)
+ }
}
func expectedAttrs() *Attributes {
diff --git a/modules/git/blame_sha256_test.go b/modules/git/blame_sha256_test.go
deleted file mode 100644
index 5b6004cff20..00000000000
--- a/modules/git/blame_sha256_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2024 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package git
-
-import (
- "testing"
-
- "gitea.dev/modules/setting"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestReadingBlameOutputSha256(t *testing.T) {
- setting.AppDataPath = t.TempDir()
- ctx := t.Context()
-
- t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
- storage := mockRepository("repo5_pulls_sha256")
- repo, err := OpenRepository(ctx, storage)
- assert.NoError(t, err)
- defer repo.Close()
-
- commit, err := repo.GetCommit(t.Context(), "0b69b7bb649b5d46e14cabb6468685e5dd721290acc7ffe604d37cde57927345")
- assert.NoError(t, err)
-
- parts := []*BlamePart{
- {
- Sha: "1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca",
- Lines: []string{
- "# test_repo",
- "Test repository for testing migration from github to gitea",
- },
- },
- {
- Sha: "0b69b7bb649b5d46e14cabb6468685e5dd721290acc7ffe604d37cde57927345",
- Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"},
- PreviousSha: "1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca",
- PreviousPath: "README.md",
- },
- }
-
- for _, bypass := range []bool{false, true} {
- blameReader, err := CreateBlameReader(ctx, Sha256ObjectFormat, storage, repo, commit, "README.md", bypass)
- assert.NoError(t, err)
- assert.NotNil(t, blameReader)
- defer blameReader.Close()
-
- assert.False(t, blameReader.UsesIgnoreRevs())
-
- for _, part := range parts {
- actualPart, err := blameReader.NextPart()
- assert.NoError(t, err)
- assert.Equal(t, part, actualPart)
- }
-
- // make sure all parts have been read
- actualPart, err := blameReader.NextPart()
- assert.Nil(t, actualPart)
- assert.NoError(t, err)
- }
- })
-
- t.Run("With .git-blame-ignore-revs", func(t *testing.T) {
- storage := mockRepository("repo6_blame_sha256")
- repo, err := OpenRepository(ctx, storage)
- assert.NoError(t, err)
- defer repo.Close()
-
- full := []*BlamePart{
- {
- Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
- Lines: []string{"line", "line"},
- },
- {
- Sha: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe",
- Lines: []string{"changed line"},
- PreviousSha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
- PreviousPath: "blame.txt",
- },
- {
- Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
- Lines: []string{"line", "line", ""},
- },
- }
-
- cases := []struct {
- CommitID string
- UsesIgnoreRevs bool
- Bypass bool
- Parts []*BlamePart
- }{
- {
- CommitID: "e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3",
- UsesIgnoreRevs: true,
- Bypass: false,
- Parts: []*BlamePart{
- {
- Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
- Lines: []string{"line", "line", "changed line", "line", "line", ""},
- },
- },
- },
- {
- CommitID: "e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3",
- UsesIgnoreRevs: false,
- Bypass: true,
- Parts: full,
- },
- {
- CommitID: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe",
- UsesIgnoreRevs: false,
- Bypass: false,
- Parts: full,
- },
- {
- CommitID: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe",
- UsesIgnoreRevs: false,
- Bypass: false,
- Parts: full,
- },
- }
-
- objectFormat, err := repo.GetObjectFormat(t.Context())
- assert.NoError(t, err)
- for _, c := range cases {
- commit, err := repo.GetCommit(t.Context(), c.CommitID)
- assert.NoError(t, err)
- blameReader, err := CreateBlameReader(ctx, objectFormat, storage, repo, commit, "blame.txt", c.Bypass)
- assert.NoError(t, err)
- assert.NotNil(t, blameReader)
- defer blameReader.Close()
-
- assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs())
-
- for _, part := range c.Parts {
- actualPart, err := blameReader.NextPart()
- assert.NoError(t, err)
- assert.Equal(t, part, actualPart)
- }
-
- // make sure all parts have been read
- actualPart, err := blameReader.NextPart()
- assert.Nil(t, actualPart)
- assert.NoError(t, err)
- }
- })
-}
diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go
index 7cf8d92e919..83d1f07b66f 100644
--- a/modules/git/blame_test.go
+++ b/modules/git/blame_test.go
@@ -24,137 +24,170 @@ func TestLineBlameCommitID(t *testing.T) {
}
func TestReadingBlameOutput(t *testing.T) {
- setting.AppDataPath = t.TempDir()
- ctx := t.Context()
+ for _, scenario := range []struct {
+ name string
+ repositorySuffix string
+ objectFormat ObjectFormat
+ readmeCommit string
+ readmePrevious string
+ blameCommit string
+ blameChanged string
+ blameOriginal string
+ }{
+ {
+ name: "sha1",
+ objectFormat: Sha1ObjectFormat,
+ readmeCommit: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
+ readmePrevious: "72866af952e98d02a73003501836074b286a78f6",
+ blameCommit: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7",
+ blameChanged: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
+ blameOriginal: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
+ },
+ {
+ name: "sha256",
+ repositorySuffix: "_sha256",
+ objectFormat: Sha256ObjectFormat,
+ readmeCommit: "0b69b7bb649b5d46e14cabb6468685e5dd721290acc7ffe604d37cde57927345",
+ readmePrevious: "1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca",
+ blameCommit: "e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3",
+ blameChanged: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe",
+ blameOriginal: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
+ },
+ } {
+ t.Run(scenario.name, func(t *testing.T) {
+ setting.AppDataPath = t.TempDir()
+ ctx := t.Context()
- t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
- storage := mockRepository("repo5_pulls")
- repo, err := OpenRepository(ctx, storage)
- assert.NoError(t, err)
- defer repo.Close()
- commit, err := repo.GetCommit(t.Context(), "f32b0a9dfd09a60f616f29158f772cedd89942d2")
- assert.NoError(t, err)
-
- parts := []*BlamePart{
- {
- Sha: "72866af952e98d02a73003501836074b286a78f6",
- Lines: []string{
- "# test_repo",
- "Test repository for testing migration from github to gitea",
- },
- },
- {
- Sha: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
- Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"},
- PreviousSha: "72866af952e98d02a73003501836074b286a78f6",
- PreviousPath: "README.md",
- },
- }
-
- for _, bypass := range []bool{false, true} {
- blameReader, err := CreateBlameReader(ctx, Sha1ObjectFormat, storage, repo, commit, "README.md", bypass)
- assert.NoError(t, err)
- assert.NotNil(t, blameReader)
- defer blameReader.Close()
-
- assert.False(t, blameReader.UsesIgnoreRevs())
-
- for _, part := range parts {
- actualPart, err := blameReader.NextPart()
+ t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
+ storage := mockRepository("repo5_pulls" + scenario.repositorySuffix)
+ repo, err := OpenRepository(ctx, storage)
+ assert.NoError(t, err)
+ defer repo.Close()
+ commit, err := repo.GetCommit(t.Context(), scenario.readmeCommit)
assert.NoError(t, err)
- assert.Equal(t, part, actualPart)
- }
- // make sure all parts have been read
- actualPart, err := blameReader.NextPart()
- assert.Nil(t, actualPart)
- assert.NoError(t, err)
- }
- })
-
- t.Run("With .git-blame-ignore-revs", func(t *testing.T) {
- storage := mockRepository("repo6_blame")
- repo, err := OpenRepository(ctx, storage)
- assert.NoError(t, err)
- defer repo.Close()
-
- full := []*BlamePart{
- {
- Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
- Lines: []string{"line", "line"},
- },
- {
- Sha: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
- Lines: []string{"changed line"},
- PreviousSha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
- PreviousPath: "blame.txt",
- },
- {
- Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
- Lines: []string{"line", "line", ""},
- },
- }
-
- cases := []struct {
- CommitID string
- UsesIgnoreRevs bool
- Bypass bool
- Parts []*BlamePart
- }{
- {
- CommitID: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7",
- UsesIgnoreRevs: true,
- Bypass: false,
- Parts: []*BlamePart{
+ parts := []*BlamePart{
{
- Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
- Lines: []string{"line", "line", "changed line", "line", "line", ""},
+ Sha: scenario.readmePrevious,
+ Lines: []string{
+ "# test_repo",
+ "Test repository for testing migration from github to gitea",
+ },
},
- },
- },
- {
- CommitID: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7",
- UsesIgnoreRevs: false,
- Bypass: true,
- Parts: full,
- },
- {
- CommitID: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
- UsesIgnoreRevs: false,
- Bypass: false,
- Parts: full,
- },
- {
- CommitID: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
- UsesIgnoreRevs: false,
- Bypass: false,
- Parts: full,
- },
- }
+ {
+ Sha: scenario.readmeCommit,
+ Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"},
+ PreviousSha: scenario.readmePrevious,
+ PreviousPath: "README.md",
+ },
+ }
- objectFormat, err := repo.GetObjectFormat(t.Context())
- assert.NoError(t, err)
- for _, c := range cases {
- commit, err := repo.GetCommit(t.Context(), c.CommitID)
- assert.NoError(t, err)
+ for _, bypass := range []bool{false, true} {
+ blameReader, err := CreateBlameReader(ctx, scenario.objectFormat, storage, repo, commit, "README.md", bypass)
+ assert.NoError(t, err)
+ assert.NotNil(t, blameReader)
+ defer blameReader.Close()
- blameReader, err := CreateBlameReader(ctx, objectFormat, storage, repo, commit, "blame.txt", c.Bypass)
- assert.NoError(t, err)
- assert.NotNil(t, blameReader)
- defer blameReader.Close()
+ assert.False(t, blameReader.UsesIgnoreRevs())
- assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs())
+ for _, part := range parts {
+ actualPart, err := blameReader.NextPart()
+ assert.NoError(t, err)
+ assert.Equal(t, part, actualPart)
+ }
- for _, part := range c.Parts {
- actualPart, err := blameReader.NextPart()
+ // make sure all parts have been read
+ actualPart, err := blameReader.NextPart()
+ assert.Nil(t, actualPart)
+ assert.NoError(t, err)
+ }
+ })
+
+ t.Run("With .git-blame-ignore-revs", func(t *testing.T) {
+ storage := mockRepository("repo6_blame" + scenario.repositorySuffix)
+ repo, err := OpenRepository(ctx, storage)
assert.NoError(t, err)
- assert.Equal(t, part, actualPart)
- }
+ defer repo.Close()
- // make sure all parts have been read
- actualPart, err := blameReader.NextPart()
- assert.Nil(t, actualPart)
- assert.NoError(t, err)
- }
- })
+ full := []*BlamePart{
+ {
+ Sha: scenario.blameOriginal,
+ Lines: []string{"line", "line"},
+ },
+ {
+ Sha: scenario.blameChanged,
+ Lines: []string{"changed line"},
+ PreviousSha: scenario.blameOriginal,
+ PreviousPath: "blame.txt",
+ },
+ {
+ Sha: scenario.blameOriginal,
+ Lines: []string{"line", "line", ""},
+ },
+ }
+
+ cases := []struct {
+ CommitID string
+ UsesIgnoreRevs bool
+ Bypass bool
+ Parts []*BlamePart
+ }{
+ {
+ CommitID: scenario.blameCommit,
+ UsesIgnoreRevs: true,
+ Bypass: false,
+ Parts: []*BlamePart{
+ {
+ Sha: scenario.blameOriginal,
+ Lines: []string{"line", "line", "changed line", "line", "line", ""},
+ },
+ },
+ },
+ {
+ CommitID: scenario.blameCommit,
+ UsesIgnoreRevs: false,
+ Bypass: true,
+ Parts: full,
+ },
+ {
+ CommitID: scenario.blameChanged,
+ UsesIgnoreRevs: false,
+ Bypass: false,
+ Parts: full,
+ },
+ {
+ CommitID: scenario.blameChanged,
+ UsesIgnoreRevs: false,
+ Bypass: false,
+ Parts: full,
+ },
+ }
+
+ objectFormat, err := repo.GetObjectFormat(t.Context())
+ assert.NoError(t, err)
+ for _, c := range cases {
+ commit, err := repo.GetCommit(t.Context(), c.CommitID)
+ assert.NoError(t, err)
+
+ blameReader, err := CreateBlameReader(ctx, objectFormat, storage, repo, commit, "blame.txt", c.Bypass)
+ assert.NoError(t, err)
+ assert.NotNil(t, blameReader)
+ defer blameReader.Close()
+
+ assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs())
+
+ for _, part := range c.Parts {
+ actualPart, err := blameReader.NextPart()
+ assert.NoError(t, err)
+ assert.Equal(t, part, actualPart)
+ }
+
+ // make sure all parts have been read
+ actualPart, err := blameReader.NextPart()
+ assert.Nil(t, actualPart)
+ assert.NoError(t, err)
+ }
+ })
+ })
+ }
}
diff --git a/modules/git/commit2_test.go b/modules/git/commit2_test.go
index eb5bee4808e..8f73303ff9e 100644
--- a/modules/git/commit2_test.go
+++ b/modules/git/commit2_test.go
@@ -11,60 +11,29 @@ import (
func TestCommitsCount(t *testing.T) {
bareRepo1 := mockRepository("repo1_bare")
-
- commitsCount, err := CommitsCount(t.Context(), bareRepo1,
- CommitsCountOptions{
- Revision: []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"},
- })
-
- assert.NoError(t, err)
- assert.Equal(t, int64(3), commitsCount)
-}
-
-func TestCommitsCountWithSinceUntil(t *testing.T) {
- bareRepo1 := mockRepository("repo1_bare")
revision := []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"}
// The three commits on this revision are dated 2018-04-18, 2017-12-19 and 2017-12-19.
- cases := []struct {
+ for _, tc := range []struct {
name string
- since string
- until string
+ options CommitsCountOptions
expected int64
}{
- {name: "no filter", expected: 3},
- {name: "since keeps newer commits", since: "2018-01-01", expected: 1},
- {name: "until keeps older commits", until: "2018-01-01", expected: 2},
- {name: "since and until bound the range", since: "2017-12-19T22:16:00-08:00", until: "2018-01-01", expected: 1},
- }
- for _, tc := range cases {
+ {"revision", CommitsCountOptions{Revision: revision}, 3},
+ {"no filter", CommitsCountOptions{Revision: revision}, 3},
+ {"since keeps newer commits", CommitsCountOptions{Revision: revision, Since: "2018-01-01"}, 1},
+ {"until keeps older commits", CommitsCountOptions{Revision: revision, Until: "2018-01-01"}, 2},
+ {"since and until bound the range", CommitsCountOptions{Revision: revision, Since: "2017-12-19T22:16:00-08:00", Until: "2018-01-01"}, 1},
+ {"without base", CommitsCountOptions{Not: "master", Revision: []string{"branch1"}}, 2},
+ } {
t.Run(tc.name, func(t *testing.T) {
- commitsCount, err := CommitsCount(t.Context(), bareRepo1,
- CommitsCountOptions{
- Revision: revision,
- Since: tc.since,
- Until: tc.until,
- })
-
+ commitsCount, err := CommitsCount(t.Context(), bareRepo1, tc.options)
assert.NoError(t, err)
assert.Equal(t, tc.expected, commitsCount)
})
}
}
-func TestCommitsCountWithoutBase(t *testing.T) {
- bareRepo1 := mockRepository("repo1_bare")
-
- commitsCount, err := CommitsCount(t.Context(), bareRepo1,
- CommitsCountOptions{
- Not: "master",
- Revision: []string{"branch1"},
- })
-
- assert.NoError(t, err)
- assert.Equal(t, int64(2), commitsCount)
-}
-
func TestGetLatestCommitTime(t *testing.T) {
bareRepo1 := mockRepository("repo1_bare")
lct, err := GetLatestCommitTime(t.Context(), bareRepo1)
diff --git a/modules/git/commit_file_test.go b/modules/git/commit_file_test.go
index d595958c13d..19466e13ead 100644
--- a/modules/git/commit_file_test.go
+++ b/modules/git/commit_file_test.go
@@ -12,7 +12,7 @@ import (
func TestParseCommitFileStatus(t *testing.T) {
type testcase struct {
- output string
+ outputs []string
added []string
removed []string
modified []string
@@ -21,7 +21,11 @@ func TestParseCommitFileStatus(t *testing.T) {
kases := []testcase{
{
// Merge commit
- output: "MM\x00options/locale/locale_en-US.ini\x00",
+ outputs: []string{
+ "MM\x00options/locale/locale_en-US.ini\x00",
+ // git 1.7.2 adds an unnecessary \x00 on merge commit
+ "\x00MM\x00options/locale/locale_en-US.ini\x00",
+ },
modified: []string{
"options/locale/locale_en-US.ini",
},
@@ -30,7 +34,11 @@ func TestParseCommitFileStatus(t *testing.T) {
},
{
// Spaces commit
- output: "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
+ outputs: []string{
+ "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
+ // git 1.7.2 adds an unnecessary \n on normal commit
+ "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
+ },
removed: []string{
"b",
"b b/b",
@@ -43,7 +51,7 @@ func TestParseCommitFileStatus(t *testing.T) {
},
{
// larger commit
- output: "M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00",
+ outputs: []string{"M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00"},
modified: []string{
"go.mod",
"go.sum",
@@ -81,37 +89,17 @@ func TestParseCommitFileStatus(t *testing.T) {
},
removed: []string{},
},
- {
- // git 1.7.2 adds an unnecessary \x00 on merge commit
- output: "\x00MM\x00options/locale/locale_en-US.ini\x00",
- modified: []string{
- "options/locale/locale_en-US.ini",
- },
- added: []string{},
- removed: []string{},
- },
- {
- // git 1.7.2 adds an unnecessary \n on normal commit
- output: "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00",
- removed: []string{
- "b",
- "b b/b",
- },
- modified: []string{},
- added: []string{
- "b b/b b/b b/b",
- "b b/b b/b b/b b/b",
- },
- },
}
for _, kase := range kases {
- fileStatus := NewCommitFileStatus()
- parseCommitFileStatus(fileStatus, strings.NewReader(kase.output))
+ for _, output := range kase.outputs {
+ fileStatus := NewCommitFileStatus()
+ parseCommitFileStatus(fileStatus, strings.NewReader(output))
- assert.Equal(t, kase.added, fileStatus.Added)
- assert.Equal(t, kase.removed, fileStatus.Removed)
- assert.Equal(t, kase.modified, fileStatus.Modified)
+ assert.Equal(t, kase.added, fileStatus.Added)
+ assert.Equal(t, kase.removed, fileStatus.Removed)
+ assert.Equal(t, kase.modified, fileStatus.Modified)
+ }
}
}
@@ -141,35 +129,23 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
func TestGetCommitFileStatusMergesSha256(t *testing.T) {
bareRepo6Sha256 := mockRepository("repo6_merge_sha256")
- commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6Sha256, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1")
- assert.NoError(t, err)
-
- expected := CommitFileStatus{
- []string{
- "add_file.txt",
+ for _, tc := range []struct {
+ commitID string
+ expected CommitFileStatus
+ }{
+ {
+ "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1",
+ CommitFileStatus{[]string{"add_file.txt"}, []string{}, []string{"to_modify.txt"}},
},
- []string{},
- []string{
- "to_modify.txt",
+ {
+ "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172",
+ CommitFileStatus{[]string{}, []string{"to_remove.txt"}, []string{}},
},
+ } {
+ commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6Sha256, tc.commitID)
+ assert.NoError(t, err)
+ assert.Equal(t, tc.expected.Added, commitFileStatus.Added)
+ assert.Equal(t, tc.expected.Removed, commitFileStatus.Removed)
+ assert.Equal(t, tc.expected.Modified, commitFileStatus.Modified)
}
-
- assert.Equal(t, expected.Added, commitFileStatus.Added)
- assert.Equal(t, expected.Removed, commitFileStatus.Removed)
- assert.Equal(t, expected.Modified, commitFileStatus.Modified)
-
- expected = CommitFileStatus{
- []string{},
- []string{
- "to_remove.txt",
- },
- []string{},
- }
-
- commitFileStatus, err = GetCommitFileStatus(t.Context(), bareRepo6Sha256, "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172")
- assert.NoError(t, err)
-
- assert.Equal(t, expected.Added, commitFileStatus.Added)
- assert.Equal(t, expected.Removed, commitFileStatus.Removed)
- assert.Equal(t, expected.Modified, commitFileStatus.Modified)
}
diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go
index e725eb2b38e..dcdc1deb629 100644
--- a/modules/git/commit_sha256_test.go
+++ b/modules/git/commit_sha256_test.go
@@ -5,11 +5,9 @@ package git
import (
"path/filepath"
- "strings"
"testing"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
func TestGetFullCommitIDSha256(t *testing.T) {
@@ -26,74 +24,6 @@ func TestGetFullCommitIDErrorSha256(t *testing.T) {
}
}
-func TestCommitFromReaderSha256(t *testing.T) {
- commitString := `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
-parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
-author Adam Majer
1698676906 +0100
-committer Adam Majer 1698676906 +0100
-gpgsig-sha256 -----BEGIN PGP SIGNATURE-----
-` + " " + `
- iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz
- dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd
- aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK
- WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx
- 1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4
- JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP
- oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6
- U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy
- zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI
- VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
- HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
- 8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
- =xybZ
- -----END PGP SIGNATURE-----
-
-signed commit`
-
- sha := &Sha256Hash{
- 0x94, 0x33, 0xb2, 0xa6, 0x2b, 0x96, 0x4c, 0x17, 0xa4, 0x48, 0x5a, 0xe1, 0x80, 0xf4, 0x5f, 0x59,
- 0x5d, 0x3e, 0x69, 0xd3, 0x1b, 0x78, 0x60, 0x87, 0x77, 0x5e, 0x28, 0xc6, 0xb6, 0x39, 0x9d, 0xf0,
- }
- gitRepo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare_sha256"))
- assert.NoError(t, err)
- assert.NotNil(t, gitRepo)
- defer gitRepo.Close()
-
- commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString))
- assert.NoError(t, err)
- require.NotNil(t, commitFromReader)
- assert.EqualValues(t, sha, commitFromReader.ID)
- assert.Equal(t, `-----BEGIN PGP SIGNATURE-----
-
-iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz
-dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd
-aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK
-WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx
-1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4
-JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP
-oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6
-U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy
-zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI
-VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
-HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
-8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
-=xybZ
------END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
- assert.Equal(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
-parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
-author Adam Majer 1698676906 +0100
-committer Adam Majer 1698676906 +0100
-
-signed commit`, commitFromReader.Signature.Payload)
- assert.Equal(t, "Adam Majer ", commitFromReader.Author.String())
-
- commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n"))
- assert.NoError(t, err)
- commitFromReader.CommitMessage.MessageRaw += "\n\n"
- commitFromReader.Signature.Payload += "\n\n"
- assert.Equal(t, commitFromReader, commitFromReader2)
-}
-
func TestHasPreviousCommitSha256(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go
index 6896ce15564..15c2e845706 100644
--- a/modules/git/commit_test.go
+++ b/modules/git/commit_test.go
@@ -28,7 +28,15 @@ func TestGetFullCommitIDError(t *testing.T) {
}
func TestCommitFromReader(t *testing.T) {
- commitString := `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
+ for _, testCase := range []struct {
+ name, repo string
+ sha ObjectID
+ commitString, signature, payload, author string
+ }{
+ {
+ "TestCommitFromReader", "repo1_bare",
+ &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2},
+ `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
parent 37991dec2c8e592043f47155ce4808d4580f9123
author silverwind 1563741793 +0200
committer silverwind 1563741793 +0200
@@ -49,19 +57,8 @@ gpgsig -----BEGIN PGP SIGNATURE-----
=FRsO
-----END PGP SIGNATURE-----
-empty commit`
-
- sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
- gitRepo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
- assert.NoError(t, err)
- assert.NotNil(t, gitRepo)
- defer gitRepo.Close()
-
- commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString))
- assert.NoError(t, err)
- require.NotNil(t, commitFromReader)
- assert.EqualValues(t, sha, commitFromReader.ID)
- assert.Equal(t, `-----BEGIN PGP SIGNATURE-----
+empty commit`,
+ `-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEWPb2jX6FS2mqyJRQLmK0HJOGlEMFAl00zmEACgkQLmK0HJOG
lEMDFBAAhQKKqLD1VICygJMEB8t1gBmNLgvziOLfpX4KPWdPtBk3v/QJ7OrfMrVK
@@ -76,24 +73,19 @@ sD53z/f0J+We4VZjY+pidvA9BGZPFVdR3wd3xGs8/oH6UWaLJAMGkLG6dDb3qDLm
mfeFhT57UbE4qukTDIQ0Y0WM40UYRTakRaDY7ubhXgLgx09Cnp9XTVMsHgT6j9/i
1pxsB104XLWjQHTjr1JtiaBQEwFh9r2OKTcpvaLcbNtYpo7CzOs=
=FRsO
------END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
- assert.Equal(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
+-----END PGP SIGNATURE-----`,
+ `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
parent 37991dec2c8e592043f47155ce4808d4580f9123
author silverwind 1563741793 +0200
committer silverwind 1563741793 +0200
-empty commit`, commitFromReader.Signature.Payload)
- assert.Equal(t, "silverwind ", commitFromReader.Author.String())
-
- commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n"))
- assert.NoError(t, err)
- commitFromReader.CommitMessage.MessageRaw += "\n\n"
- commitFromReader.Signature.Payload += "\n\n"
- assert.Equal(t, commitFromReader, commitFromReader2)
-}
-
-func TestCommitWithEncodingFromReader(t *testing.T) {
- commitString := `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
+empty commit`,
+ "silverwind ",
+ },
+ {
+ "TestCommitWithEncodingFromReader", "repo1_bare",
+ &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2},
+ strings.ReplaceAll(`tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R 1711702962 +0100
committer KN4CK3R 1711702962 +0100
@@ -113,19 +105,8 @@ gpgsig -----BEGIN PGP SIGNATURE-----
=r3UU
-----END PGP SIGNATURE-----
-ISO-8859-1`
- commitString = strings.ReplaceAll(commitString, "", " ")
- sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
- gitRepo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
- assert.NoError(t, err)
- assert.NotNil(t, gitRepo)
- defer gitRepo.Close()
-
- commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString))
- assert.NoError(t, err)
- require.NotNil(t, commitFromReader)
- assert.EqualValues(t, sha, commitFromReader.ID)
- assert.Equal(t, `-----BEGIN PGP SIGNATURE-----
+ISO-8859-1`, "", " "),
+ `-----BEGIN PGP SIGNATURE-----
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
@@ -138,21 +119,90 @@ SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
jw4YcO5u
=r3UU
------END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
- assert.Equal(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
+-----END PGP SIGNATURE-----`,
+ `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R 1711702962 +0100
committer KN4CK3R 1711702962 +0100
encoding ISO-8859-1
-ISO-8859-1`, commitFromReader.Signature.Payload)
- assert.Equal(t, "KN4CK3R ", commitFromReader.Author.String())
+ISO-8859-1`,
+ "KN4CK3R ",
+ },
+ {
+ "TestCommitFromReaderSha256", "repo1_bare_sha256",
+ &Sha256Hash{
+ 0x94, 0x33, 0xb2, 0xa6, 0x2b, 0x96, 0x4c, 0x17, 0xa4, 0x48, 0x5a, 0xe1, 0x80, 0xf4, 0x5f, 0x59,
+ 0x5d, 0x3e, 0x69, 0xd3, 0x1b, 0x78, 0x60, 0x87, 0x77, 0x5e, 0x28, 0xc6, 0xb6, 0x39, 0x9d, 0xf0,
+ },
+ `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
+parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
+author Adam Majer 1698676906 +0100
+committer Adam Majer 1698676906 +0100
+gpgsig-sha256 -----BEGIN PGP SIGNATURE-----
+` + " " + `
+ iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz
+ dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd
+ aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK
+ WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx
+ 1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4
+ JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP
+ oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6
+ U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy
+ zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI
+ VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
+ HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
+ 8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
+ =xybZ
+ -----END PGP SIGNATURE-----
- commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n"))
- assert.NoError(t, err)
- commitFromReader.CommitMessage.MessageRaw += "\n\n"
- commitFromReader.Signature.Payload += "\n\n"
- assert.Equal(t, commitFromReader, commitFromReader2)
+signed commit`,
+ `-----BEGIN PGP SIGNATURE-----
+
+iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz
+dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd
+aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK
+WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx
+1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4
+JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP
+oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6
+U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy
+zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI
+VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
+HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
+8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
+=xybZ
+-----END PGP SIGNATURE-----`,
+ `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
+parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
+author Adam Majer 1698676906 +0100
+committer Adam Majer 1698676906 +0100
+
+signed commit`,
+ "Adam Majer ",
+ },
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ gitRepo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, testCase.repo))
+ assert.NoError(t, err)
+ assert.NotNil(t, gitRepo)
+ defer gitRepo.Close()
+
+ commitFromReader, err := CommitFromReader(testCase.sha, strings.NewReader(testCase.commitString))
+ assert.NoError(t, err)
+ require.NotNil(t, commitFromReader)
+ assert.Equal(t, testCase.sha, commitFromReader.ID)
+ assert.Equal(t, testCase.signature, commitFromReader.Signature.Signature)
+ assert.Equal(t, testCase.payload, commitFromReader.Signature.Payload)
+ assert.Equal(t, testCase.author, commitFromReader.Author.String())
+
+ commitFromReader2, err := CommitFromReader(testCase.sha, strings.NewReader(testCase.commitString+"\n\n"))
+ assert.NoError(t, err)
+ commitFromReader.CommitMessage.MessageRaw += "\n\n"
+ commitFromReader.Signature.Payload += "\n\n"
+ assert.Equal(t, commitFromReader, commitFromReader2)
+ })
+ }
}
func TestHasPreviousCommit(t *testing.T) {
diff --git a/modules/git/compare_test.go b/modules/git/compare_test.go
index 64ac71f2858..ac4fe894afa 100644
--- a/modules/git/compare_test.go
+++ b/modules/git/compare_test.go
@@ -43,96 +43,47 @@ Hello from 2
func TestRepoGetDivergingCommits(t *testing.T) {
repo := mockRepository("repo1_bare")
- do, err := GetDivergingCommits(t.Context(), repo, "master", "branch2")
- assert.NoError(t, err)
- assert.Equal(t, &DivergeObject{
- Ahead: 1,
- Behind: 5,
- }, do)
-
- do, err = GetDivergingCommits(t.Context(), repo, "master", "master")
- assert.NoError(t, err)
- assert.Equal(t, &DivergeObject{
- Ahead: 0,
- Behind: 0,
- }, do)
-
- do, err = GetDivergingCommits(t.Context(), repo, "master", "test")
- assert.NoError(t, err)
- assert.Equal(t, &DivergeObject{
- Ahead: 0,
- Behind: 2,
- }, do)
+ for _, testcase := range []struct {
+ branch string
+ expected DivergeObject
+ }{
+ {"branch2", DivergeObject{Ahead: 1, Behind: 5}},
+ {"master", DivergeObject{Ahead: 0, Behind: 0}},
+ {"test", DivergeObject{Ahead: 0, Behind: 2}},
+ } {
+ do, err := GetDivergingCommits(t.Context(), repo, "master", testcase.branch)
+ assert.NoError(t, err)
+ assert.Equal(t, &testcase.expected, do)
+ }
}
func TestGetCommitIDsBetweenReverse(t *testing.T) {
repo := mockRepository("repo1_bare")
-
- // tests raw commit IDs
- commitIDs, err := GetCommitIDsBetweenReverse(t.Context(), repo,
- "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
- "ce064814f4a0d337b333e646ece456cd39fab612",
- "",
- 100,
- )
- assert.NoError(t, err)
- assert.Equal(t, []string{
+ startRef := "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2"
+ endRef := "ce064814f4a0d337b333e646ece456cd39fab612"
+ expectedCommitIDs := []string{
"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0",
"6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1",
"37991dec2c8e592043f47155ce4808d4580f9123",
"feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
"ce064814f4a0d337b333e646ece456cd39fab612",
- }, commitIDs)
-
- commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo,
- "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
- "ce064814f4a0d337b333e646ece456cd39fab612",
- "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1",
- 100,
- )
- assert.NoError(t, err)
- assert.Equal(t, []string{
- "37991dec2c8e592043f47155ce4808d4580f9123",
- "feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
- "ce064814f4a0d337b333e646ece456cd39fab612",
- }, commitIDs)
-
- commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo,
- "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
- "ce064814f4a0d337b333e646ece456cd39fab612",
- "",
- 3,
- )
- assert.NoError(t, err)
- assert.Equal(t, []string{
- "37991dec2c8e592043f47155ce4808d4580f9123",
- "feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
- "ce064814f4a0d337b333e646ece456cd39fab612",
- }, commitIDs)
-
- // test branch names instead of raw commit IDs.
- commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo,
- "test",
- "master",
- "",
- 100,
- )
- assert.NoError(t, err)
- assert.Equal(t, []string{
- "feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
- "ce064814f4a0d337b333e646ece456cd39fab612",
- }, commitIDs)
-
- // add notref to exclude test
- commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo,
- "test",
- "master",
- "test",
- 100,
- )
- assert.NoError(t, err)
- assert.Equal(t, []string{
- "feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
- "ce064814f4a0d337b333e646ece456cd39fab612",
- }, commitIDs)
+ }
+ for _, testcase := range []struct {
+ startRef, endRef, notRef string
+ limit int
+ expected []string
+ }{
+ // tests raw commit IDs
+ {startRef, endRef, "", 100, expectedCommitIDs},
+ {startRef, endRef, expectedCommitIDs[1], 100, expectedCommitIDs[2:]},
+ {startRef, endRef, "", 3, expectedCommitIDs[2:]},
+ // test branch names instead of raw commit IDs.
+ {"test", "master", "", 100, expectedCommitIDs[3:]},
+ // add notref to exclude test
+ {"test", "master", "test", 100, expectedCommitIDs[3:]},
+ } {
+ commitIDs, err := GetCommitIDsBetweenReverse(t.Context(), repo, testcase.startRef, testcase.endRef, testcase.notRef, testcase.limit)
+ assert.NoError(t, err)
+ assert.Equal(t, testcase.expected, commitIDs)
+ }
}
diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go
index 3b6e64038b2..01fcabe98f8 100644
--- a/modules/git/grep_test.go
+++ b/modules/git/grep_test.go
@@ -20,9 +20,7 @@ func TestGrepSearch(t *testing.T) {
assert.NoError(t, err)
defer repo.Close()
- res, err := GrepSearch(t.Context(), repo, "void", GrepOptions{})
- assert.NoError(t, err)
- assert.Equal(t, []*GrepResult{
+ expectedResults := []*GrepResult{
{
Filename: "java-hello/main.java",
LineNumbers: []int{3},
@@ -33,49 +31,29 @@ func TestGrepSearch(t *testing.T) {
LineNumbers: []int{3},
LineCodes: []string{" public static void main(String[] args)"},
},
- }, res)
+ }
+ for _, testcase := range []struct {
+ options GrepOptions
+ expected []*GrepResult
+ }{
+ {GrepOptions{}, expectedResults},
+ {GrepOptions{PathspecList: []string{":(glob)java-hello/*"}}, expectedResults[:1]},
+ {GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}}, expectedResults[1:]},
+ {GrepOptions{MaxResultLimit: 1}, expectedResults[:1]},
+ {GrepOptions{MaxResultLimit: 1, MaxLineLength: 39}, []*GrepResult{
+ {
+ Filename: "java-hello/main.java",
+ LineNumbers: []int{3},
+ LineCodes: []string{" public static void main(String[] arg"},
+ },
+ }},
+ } {
+ res, err := GrepSearch(t.Context(), repo, "void", testcase.options)
+ assert.NoError(t, err)
+ assert.Equal(t, testcase.expected, res)
+ }
- res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}})
- assert.NoError(t, err)
- assert.Equal(t, []*GrepResult{
- {
- Filename: "java-hello/main.java",
- LineNumbers: []int{3},
- LineCodes: []string{" public static void main(String[] args)"},
- },
- }, res)
-
- res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}})
- assert.NoError(t, err)
- assert.Equal(t, []*GrepResult{
- {
- Filename: "main.vendor.java",
- LineNumbers: []int{3},
- LineCodes: []string{" public static void main(String[] args)"},
- },
- }, res)
-
- res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{MaxResultLimit: 1})
- assert.NoError(t, err)
- assert.Equal(t, []*GrepResult{
- {
- Filename: "java-hello/main.java",
- LineNumbers: []int{3},
- LineCodes: []string{" public static void main(String[] args)"},
- },
- }, res)
-
- res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{MaxResultLimit: 1, MaxLineLength: 39})
- assert.NoError(t, err)
- assert.Equal(t, []*GrepResult{
- {
- Filename: "java-hello/main.java",
- LineNumbers: []int{3},
- LineCodes: []string{" public static void main(String[] arg"},
- },
- }, res)
-
- res, err = GrepSearch(t.Context(), repo, "no-such-content", GrepOptions{})
+ res, err := GrepSearch(t.Context(), repo, "no-such-content", GrepOptions{})
assert.NoError(t, err)
assert.Empty(t, res)
diff --git a/modules/git/parse_treeentry_test.go b/modules/git/parse_treeentry_test.go
index 5b81b49edda..93cbb999915 100644
--- a/modules/git/parse_treeentry_test.go
+++ b/modules/git/parse_treeentry_test.go
@@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestParseTreeEntriesLong(t *testing.T) {
+func TestParseTreeEntries(t *testing.T) {
testCases := []struct {
Input string
Expected []*TreeEntry
@@ -53,22 +53,6 @@ func TestParseTreeEntriesLong(t *testing.T) {
},
},
},
- }
- for _, testCase := range testCases {
- entries, err := ParseTreeEntries([]byte(testCase.Input))
- assert.NoError(t, err)
- assert.Len(t, entries, len(testCase.Expected))
- for i, entry := range entries {
- assert.Equal(t, testCase.Expected[i], entry)
- }
- }
-}
-
-func TestParseTreeEntriesShort(t *testing.T) {
- testCases := []struct {
- Input string
- Expected []*TreeEntry
- }{
{
Input: `100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af README.md
040000 tree 84b90550547016f73c5dd3f50dea662389e67b6d assets
diff --git a/modules/git/repo_blob_test.go b/modules/git/repo_blob_test.go
index 9b4a40d2935..5abecd0ab6c 100644
--- a/modules/git/repo_blob_test.go
+++ b/modules/git/repo_blob_test.go
@@ -40,30 +40,23 @@ func TestRepository_GetBlob_Found(t *testing.T) {
}
}
-func TestRepository_GetBlob_NotExist(t *testing.T) {
- repoPath := filepath.Join(testReposDir, "repo1_bare")
- r, err := OpenRepositoryLocal(t.Context(), repoPath)
- assert.NoError(t, err)
- defer r.Close()
+func TestRepository_GetBlob_Errors(t *testing.T) {
+ for _, testCase := range []struct {
+ name string
+ id string
+ err error
+ }{
+ {"NotExist", "0000000000000000000000000000000000000000", ErrNotExist{"0000000000000000000000000000000000000000", ""}},
+ {"NoId", "", fmt.Errorf("length %d has no matched object format: %s", len(""), "")},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ r, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
+ assert.NoError(t, err)
+ defer r.Close()
- testCase := "0000000000000000000000000000000000000000"
- testError := ErrNotExist{testCase, ""}
-
- blob, err := r.GetBlob(testCase)
- assert.Nil(t, blob)
- assert.EqualError(t, err, testError.Error())
-}
-
-func TestRepository_GetBlob_NoId(t *testing.T) {
- repoPath := filepath.Join(testReposDir, "repo1_bare")
- r, err := OpenRepositoryLocal(t.Context(), repoPath)
- assert.NoError(t, err)
- defer r.Close()
-
- testCase := ""
- testError := fmt.Errorf("length %d has no matched object format: %s", len(testCase), testCase)
-
- blob, err := r.GetBlob(testCase)
- assert.Nil(t, blob)
- assert.EqualError(t, err, testError.Error())
+ blob, err := r.GetBlob(testCase.id)
+ assert.Nil(t, blob)
+ assert.EqualError(t, err, testCase.err.Error())
+ })
+ }
}
diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go
index f7335d1e688..5a7e314e99e 100644
--- a/modules/git/repo_branch_test.go
+++ b/modules/git/repo_branch_test.go
@@ -4,6 +4,7 @@
package git
import (
+ "context"
"path/filepath"
"testing"
@@ -96,100 +97,37 @@ func BenchmarkGetRefsBySha(b *testing.B) {
_, _ = bareRepo5.GetRefsBySha(b.Context(), "58a4bcc53ac13e7ff76127e0fb518b5262bf09af", "")
}
-func TestRepository_IsObjectExist(t *testing.T) {
- ctx := t.Context()
- repo, err := OpenRepositoryLocal(ctx, filepath.Join(testReposDir, "repo1_bare"))
- require.NoError(t, err)
- defer repo.Close()
-
- tests := []struct {
- name string
- arg string
- want bool
+func TestRepository_Existence(t *testing.T) {
+ for _, method := range []struct {
+ name string
+ exists func(*Repository, context.Context, string) bool
+ branchExists bool
}{
- {
- name: "empty",
- arg: "",
- want: false,
- },
- {
- name: "branch",
- arg: "master",
- want: false,
- },
- {
- name: "commit hash",
- arg: "ce064814f4a0d337b333e646ece456cd39fab612",
- want: true,
- },
- {
- name: "short commit hash",
- arg: "ce06481",
- want: true,
- },
- {
- name: "blob hash",
- arg: "153f451b9ee7fa1da317ab17a127e9fd9d384310",
- want: true,
- },
- {
- name: "short blob hash",
- arg: "153f451",
- want: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equal(t, tt.want, repo.IsObjectExist(ctx, tt.arg))
- })
- }
-}
-
-func TestRepository_IsReferenceExist(t *testing.T) {
- ctx := t.Context()
- repo, err := OpenRepositoryLocal(ctx, filepath.Join(testReposDir, "repo1_bare"))
- require.NoError(t, err)
- defer repo.Close()
-
- tests := []struct {
- name string
- arg string
- want bool
- }{
- {
- name: "empty",
- arg: "",
- want: false,
- },
- {
- name: "branch",
- arg: "master",
- want: true,
- },
- {
- name: "commit hash",
- arg: "ce064814f4a0d337b333e646ece456cd39fab612",
- want: true,
- },
- {
- name: "short commit hash",
- arg: "ce06481",
- want: true,
- },
- {
- name: "blob hash",
- arg: "153f451b9ee7fa1da317ab17a127e9fd9d384310",
- want: true,
- },
- {
- name: "short blob hash",
- arg: "153f451",
- want: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equal(t, tt.want, repo.IsReferenceExist(ctx, tt.arg))
+ {"IsObjectExist", (*Repository).IsObjectExist, false},
+ {"IsReferenceExist", (*Repository).IsReferenceExist, true},
+ } {
+ t.Run(method.name, func(t *testing.T) {
+ ctx := t.Context()
+ repo, err := OpenRepositoryLocal(ctx, filepath.Join(testReposDir, "repo1_bare"))
+ require.NoError(t, err)
+ defer repo.Close()
+
+ for _, testCase := range []struct {
+ name string
+ arg string
+ want bool
+ }{
+ {"empty", "", false},
+ {"branch", "master", method.branchExists},
+ {"commit hash", "ce064814f4a0d337b333e646ece456cd39fab612", true},
+ {"short commit hash", "ce06481", true},
+ {"blob hash", "153f451b9ee7fa1da317ab17a127e9fd9d384310", true},
+ {"short blob hash", "153f451", true},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ assert.Equal(t, testCase.want, method.exists(repo, ctx, testCase.arg))
+ })
+ }
})
}
}
diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go
index b89ad03ad27..377d27f459a 100644
--- a/modules/git/repo_commit_test.go
+++ b/modules/git/repo_commit_test.go
@@ -16,91 +16,96 @@ import (
"github.com/stretchr/testify/require"
)
-func TestRepository_GetCommitBranches(t *testing.T) {
- bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
- bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
- assert.NoError(t, err)
- defer bareRepo1.Close()
-
- // these test case are specific to the repo1_bare test repo
- testCases := []struct {
- CommitID string
- ExpectedBranches []string
+func TestRepository_CommitQueries(t *testing.T) {
+ for _, testCase := range []struct {
+ name string
+ run func(*testing.T, *Repository)
}{
- {"2839944139e0de9737a044f78b0e4b40d989a9e3", []string{"branch1"}},
- {"5c80b0245c1c6f8343fa418ec374b13b5d4ee658", []string{"branch2"}},
- {"37991dec2c8e592043f47155ce4808d4580f9123", []string{"master"}},
- {"95bb4d39648ee7e325106df01a621c530863a653", []string{"branch1", "branch2"}},
- {"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", []string{"branch2", "master"}},
- {"master", []string{"master"}},
+ {"TestRepository_GetCommitBranches", func(t *testing.T, bareRepo1 *Repository) {
+ // these test case are specific to the repo1_bare test repo
+ testCases := []struct {
+ CommitID string
+ ExpectedBranches []string
+ }{
+ {"2839944139e0de9737a044f78b0e4b40d989a9e3", []string{"branch1"}},
+ {"5c80b0245c1c6f8343fa418ec374b13b5d4ee658", []string{"branch2"}},
+ {"37991dec2c8e592043f47155ce4808d4580f9123", []string{"master"}},
+ {"95bb4d39648ee7e325106df01a621c530863a653", []string{"branch1", "branch2"}},
+ {"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", []string{"branch2", "master"}},
+ {"master", []string{"master"}},
+ }
+ for _, testCase := range testCases {
+ commit, err := bareRepo1.GetCommit(t.Context(), testCase.CommitID)
+ assert.NoError(t, err)
+ branches, err := bareRepo1.getBranches(t.Context(), nil, commit.ID.String(), 2)
+ assert.NoError(t, err)
+ assert.Equal(t, testCase.ExpectedBranches, branches)
+ }
+ }},
+ {"TestGetTagCommitWithSignature", func(t *testing.T, bareRepo1 *Repository) {
+ // both the tag and the commit are signed here, this validates only the commit signature
+ commit, err := bareRepo1.GetCommit(t.Context(), "28b55526e7100924d864dd89e35c1ea62e7a5a32")
+ assert.NoError(t, err)
+ assert.NotNil(t, commit)
+ assert.NotNil(t, commit.Signature)
+ // test that signature is not in message
+ assert.Equal(t, "signed-commit\n", commit.CommitMessage.MessageRaw)
+ }},
+ {"TestGetCommitWithBadCommitID", func(t *testing.T, bareRepo1 *Repository) {
+ commit, err := bareRepo1.GetCommit(t.Context(), "bad_branch")
+ assert.Nil(t, commit)
+ assert.Error(t, err)
+ assert.True(t, IsErrNotExist(err))
+ }},
+ {"TestIsCommitInRepo", func(t *testing.T, bareRepo1 *Repository) {
+ exist, err := IsCommitInRepo(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3")
+ assert.NoError(t, err)
+ assert.True(t, exist)
+
+ exist, err = IsCommitInRepo(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+ assert.NoError(t, err)
+ assert.False(t, exist)
+ }},
+ {"TestIsCommitInBranch", func(t *testing.T, bareRepo1 *Repository) {
+ result, err := IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch1")
+ assert.NoError(t, err)
+ assert.True(t, result)
+
+ result, err = IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch2")
+ assert.NoError(t, err)
+ assert.False(t, result)
+
+ result, err = IsCommitInBranch(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "branch1")
+ assert.NoError(t, err)
+ assert.False(t, result)
+ }},
+ {"TestGetRefCommitID", func(t *testing.T, bareRepo1 *Repository) {
+ // these test case are specific to the repo1_bare test repo
+ testCases := []struct {
+ Ref string
+ ExpectedCommitID string
+ }{
+ {RefNameFromBranch("master").String(), "ce064814f4a0d337b333e646ece456cd39fab612"},
+ {RefNameFromBranch("branch1").String(), "2839944139e0de9737a044f78b0e4b40d989a9e3"},
+ {RefNameFromTag("test").String(), "3ad28a9149a2864384548f3d17ed7f38014c9e8a"},
+ {"ce064814f4a0d337b333e646ece456cd39fab612", "ce064814f4a0d337b333e646ece456cd39fab612"},
+ }
+
+ for _, testCase := range testCases {
+ commitID, err := bareRepo1.GetRefCommitID(t.Context(), testCase.Ref)
+ if assert.NoError(t, err) {
+ assert.Equal(t, testCase.ExpectedCommitID, commitID)
+ }
+ }
+ }},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ bareRepo1, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
+ assert.NoError(t, err)
+ defer bareRepo1.Close()
+ testCase.run(t, bareRepo1)
+ })
}
- for _, testCase := range testCases {
- commit, err := bareRepo1.GetCommit(t.Context(), testCase.CommitID)
- assert.NoError(t, err)
- branches, err := bareRepo1.getBranches(t.Context(), nil, commit.ID.String(), 2)
- assert.NoError(t, err)
- assert.Equal(t, testCase.ExpectedBranches, branches)
- }
-}
-
-func TestGetTagCommitWithSignature(t *testing.T) {
- bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
- bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
- assert.NoError(t, err)
- defer bareRepo1.Close()
-
- // both the tag and the commit are signed here, this validates only the commit signature
- commit, err := bareRepo1.GetCommit(t.Context(), "28b55526e7100924d864dd89e35c1ea62e7a5a32")
- assert.NoError(t, err)
- assert.NotNil(t, commit)
- assert.NotNil(t, commit.Signature)
- // test that signature is not in message
- assert.Equal(t, "signed-commit\n", commit.CommitMessage.MessageRaw)
-}
-
-func TestGetCommitWithBadCommitID(t *testing.T) {
- bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
- bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
- assert.NoError(t, err)
- defer bareRepo1.Close()
-
- commit, err := bareRepo1.GetCommit(t.Context(), "bad_branch")
- assert.Nil(t, commit)
- assert.Error(t, err)
- assert.True(t, IsErrNotExist(err))
-}
-
-func TestIsCommitInRepo(t *testing.T) {
- bareRepo1, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
- assert.NoError(t, err)
- defer bareRepo1.Close()
-
- exist, err := IsCommitInRepo(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3")
- assert.NoError(t, err)
- assert.True(t, exist)
-
- exist, err = IsCommitInRepo(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
- assert.NoError(t, err)
- assert.False(t, exist)
-}
-
-func TestIsCommitInBranch(t *testing.T) {
- bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
- bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
- assert.NoError(t, err)
- defer bareRepo1.Close()
-
- result, err := IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch1")
- assert.NoError(t, err)
- assert.True(t, result)
-
- result, err = IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch2")
- assert.NoError(t, err)
- assert.False(t, result)
-
- result, err = IsCommitInBranch(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "branch1")
- assert.NoError(t, err)
- assert.False(t, result)
}
func TestRepository_CommitsBetween(t *testing.T) {
@@ -125,31 +130,6 @@ func TestRepository_CommitsBetween(t *testing.T) {
}
}
-func TestGetRefCommitID(t *testing.T) {
- bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
- bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
- assert.NoError(t, err)
- defer bareRepo1.Close()
-
- // these test case are specific to the repo1_bare test repo
- testCases := []struct {
- Ref string
- ExpectedCommitID string
- }{
- {RefNameFromBranch("master").String(), "ce064814f4a0d337b333e646ece456cd39fab612"},
- {RefNameFromBranch("branch1").String(), "2839944139e0de9737a044f78b0e4b40d989a9e3"},
- {RefNameFromTag("test").String(), "3ad28a9149a2864384548f3d17ed7f38014c9e8a"},
- {"ce064814f4a0d337b333e646ece456cd39fab612", "ce064814f4a0d337b333e646ece456cd39fab612"},
- }
-
- for _, testCase := range testCases {
- commitID, err := bareRepo1.GetRefCommitID(t.Context(), testCase.Ref)
- if assert.NoError(t, err) {
- assert.Equal(t, testCase.ExpectedCommitID, commitID)
- }
- }
-}
-
func TestCommitsByFileAndRange(t *testing.T) {
defer test.MockVariableValue(&setting.Git.CommitsRangeSize, 2)()
diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go
index 6c9f8e199ce..565e8150ed4 100644
--- a/modules/git/repo_tag_test.go
+++ b/modules/git/repo_tag_test.go
@@ -185,6 +185,22 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
}
func TestRepository_parseTagRef(t *testing.T) {
+ signature := `-----BEGIN PGP SIGNATURE-----
+
+aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3
+3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT
+T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU
+REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE
+slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G
+1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt
+f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx
+yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6
+kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg
+qbHDASXl
+=2yGi
+-----END PGP SIGNATURE-----
+
+`
tests := []struct {
name string
@@ -273,38 +289,8 @@ func TestRepository_parseTagRef(t *testing.T) {
* add changelog of v1.9.1
* Update CHANGELOG.md
------BEGIN PGP SIGNATURE-----
-
-aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3
-3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT
-T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU
-REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE
-slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G
-1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt
-f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx
-yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6
-kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg
-qbHDASXl
-=2yGi
------END PGP SIGNATURE-----
-
-`,
- "contents:signature": `-----BEGIN PGP SIGNATURE-----
-
-aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3
-3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT
-T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU
-REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE
-slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G
-1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt
-f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx
-yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6
-kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg
-qbHDASXl
-=2yGi
------END PGP SIGNATURE-----
-
-`,
+` + signature,
+ "contents:signature": signature,
},
want: &Tag{
@@ -315,22 +301,7 @@ qbHDASXl
Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"),
CommitMessage: CommitMessage{MessageRaw: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n"},
Signature: &CommitSignature{
- Signature: `-----BEGIN PGP SIGNATURE-----
-
-aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3
-3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT
-T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU
-REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE
-slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G
-1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt
-f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx
-yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6
-kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg
-qbHDASXl
-=2yGi
------END PGP SIGNATURE-----
-
-`,
+ Signature: signature,
Payload: `object 3325fd8a973321fd59455492976c042dde3fd1ca
type commit
tag v0.0.1
diff --git a/modules/git/signature_test.go b/modules/git/signature_test.go
index b9b5aff61b1..f9c2b58edad 100644
--- a/modules/git/signature_test.go
+++ b/modules/git/signature_test.go
@@ -11,37 +11,18 @@ import (
)
func TestParseSignatureFromCommitLine(t *testing.T) {
- tests := []struct {
- line string
- want *Signature
- }{
- {
- line: "a b 12345 +0100",
- want: &Signature{
- Name: "a b",
- Email: "c@d.com",
- When: time.Unix(12345, 0).In(time.FixedZone("", 3600)),
- },
- },
- {
- line: "bad line",
- want: &Signature{Name: "bad line"},
- },
- {
- line: "bad < line",
- want: &Signature{Name: "bad < line"},
- },
- {
- line: "bad > line",
- want: &Signature{Name: "bad > line"},
- },
- {
- line: "bad-line ",
- want: &Signature{Name: "bad-line "},
- },
- }
- for _, test := range tests {
- got := parseSignatureFromCommitLine(test.line)
- assert.Equal(t, test.want, got)
+ assert.Equal(t, &Signature{
+ Name: "a b",
+ Email: "c@d.com",
+ When: time.Unix(12345, 0).In(time.FixedZone("", 3600)),
+ }, parseSignatureFromCommitLine("a b 12345 +0100"))
+
+ for _, line := range []string{
+ "bad line",
+ "bad < line",
+ "bad > line",
+ "bad-line ",
+ } {
+ assert.Equal(t, &Signature{Name: line}, parseSignatureFromCommitLine(line))
}
}
diff --git a/modules/git/tree_entry_common_test.go b/modules/git/tree_entry_common_test.go
index 6f4abb9bed4..a37e228c5b2 100644
--- a/modules/git/tree_entry_common_test.go
+++ b/modules/git/tree_entry_common_test.go
@@ -46,32 +46,22 @@ func TestFollowLink(t *testing.T) {
assert.Nil(t, res)
}
- {
- // should error for broken links
- entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/broken_link")
+ for _, test := range []struct {
+ fullPath string
+ symlinkContent string
+ checkLink bool
+ }{
+ {"foo/broken_link", "nar/broken_link", true}, // should error for broken links
+ {"foo/outside_repo", "../../outside_repo", true}, // should error for external links
+ {"foo/link_short", "a", false}, // testing fix for short link bug
+ } {
+ entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, test.fullPath)
require.NoError(t, err)
- assert.True(t, entry.IsLink())
- res, err := EntryFollowLink(t.Context(), r, commit, "foo/broken_link", entry)
+ if test.checkLink {
+ assert.True(t, entry.IsLink())
+ }
+ res, err := EntryFollowLink(t.Context(), r, commit, test.fullPath, entry)
assert.ErrorIs(t, err, util.ErrNotExist)
- assert.Equal(t, "nar/broken_link", res.SymlinkContent)
- }
-
- {
- // should error for external links
- entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/outside_repo")
- require.NoError(t, err)
- assert.True(t, entry.IsLink())
- res, err := EntryFollowLink(t.Context(), r, commit, "foo/outside_repo", entry)
- assert.ErrorIs(t, err, util.ErrNotExist)
- assert.Equal(t, "../../outside_repo", res.SymlinkContent)
- }
-
- {
- // testing fix for short link bug
- entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/link_short")
- require.NoError(t, err)
- res, err := EntryFollowLink(t.Context(), r, commit, "foo/link_short", entry)
- assert.ErrorIs(t, err, util.ErrNotExist)
- assert.Equal(t, "a", res.SymlinkContent)
+ assert.Equal(t, test.symlinkContent, res.SymlinkContent)
}
}
diff --git a/modules/git/url/url_test.go b/modules/git/url/url_test.go
index 4f3280951ef..2267f18f4b3 100644
--- a/modules/git/url/url_test.go
+++ b/modules/git/url/url_test.go
@@ -17,156 +17,37 @@ import (
)
func TestParseGitURLs(t *testing.T) {
- kases := []struct {
- kase string
- expected *GitURL
+ for _, kase := range []struct {
+ kase, scheme, host, path string
+ user *url.Userinfo
+ extraMark int
}{
- {
- kase: "git@127.0.0.1:go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "ssh",
- User: url.User("git"),
- Host: "127.0.0.1",
- Path: "go-gitea/gitea.git",
- },
- extraMark: 1,
- },
- },
- {
- kase: "git@[fe80::14fc:cec5:c174:d88%2510]:go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "ssh",
- User: url.User("git"),
- Host: "[fe80::14fc:cec5:c174:d88%10]",
- Path: "go-gitea/gitea.git",
- },
- extraMark: 1,
- },
- },
- {
- kase: "git@[::1]:go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "ssh",
- User: url.User("git"),
- Host: "[::1]",
- Path: "go-gitea/gitea.git",
- },
- extraMark: 1,
- },
- },
- {
- kase: "git@github.com:go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "ssh",
- User: url.User("git"),
- Host: "github.com",
- Path: "go-gitea/gitea.git",
- },
- extraMark: 1,
- },
- },
- {
- kase: "ssh://git@github.com/go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "ssh",
- User: url.User("git"),
- Host: "github.com",
- Path: "/go-gitea/gitea.git",
- },
- extraMark: 0,
- },
- },
- {
- kase: "ssh://git@[::1]/go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "ssh",
- User: url.User("git"),
- Host: "[::1]",
- Path: "/go-gitea/gitea.git",
- },
- extraMark: 0,
- },
- },
- {
- kase: "/repositories/go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "file",
- Path: "/repositories/go-gitea/gitea.git",
- },
- extraMark: 2,
- },
- },
- {
- kase: "file:///repositories/go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "file",
- Path: "/repositories/go-gitea/gitea.git",
- },
- extraMark: 0,
- },
- },
- {
- kase: "https://github.com/go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "https",
- Host: "github.com",
- Path: "/go-gitea/gitea.git",
- },
- extraMark: 0,
- },
- },
- {
- kase: "https://git:git@github.com/go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "https",
- Host: "github.com",
- User: url.UserPassword("git", "git"),
- Path: "/go-gitea/gitea.git",
- },
- extraMark: 0,
- },
- },
- {
- kase: "https://[fe80::14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "https",
- Host: "[fe80::14fc:cec5:c174:d88%10]:20",
- Path: "/go-gitea/gitea.git",
- },
- extraMark: 0,
- },
- },
-
- {
- kase: "git://github.com/go-gitea/gitea.git",
- expected: &GitURL{
- URL: &url.URL{
- Scheme: "git",
- Host: "github.com",
- Path: "/go-gitea/gitea.git",
- },
- extraMark: 0,
- },
- },
- }
-
- for _, kase := range kases {
+ {"git@127.0.0.1:go-gitea/gitea.git", "ssh", "127.0.0.1", "go-gitea/gitea.git", url.User("git"), 1},
+ {"git@[fe80::14fc:cec5:c174:d88%2510]:go-gitea/gitea.git", "ssh", "[fe80::14fc:cec5:c174:d88%10]", "go-gitea/gitea.git", url.User("git"), 1},
+ {"git@[::1]:go-gitea/gitea.git", "ssh", "[::1]", "go-gitea/gitea.git", url.User("git"), 1},
+ {"git@github.com:go-gitea/gitea.git", "ssh", "github.com", "go-gitea/gitea.git", url.User("git"), 1},
+ {"ssh://git@github.com/go-gitea/gitea.git", "ssh", "github.com", "/go-gitea/gitea.git", url.User("git"), 0},
+ {"ssh://git@[::1]/go-gitea/gitea.git", "ssh", "[::1]", "/go-gitea/gitea.git", url.User("git"), 0},
+ {"/repositories/go-gitea/gitea.git", "file", "", "/repositories/go-gitea/gitea.git", nil, 2},
+ {"file:///repositories/go-gitea/gitea.git", "file", "", "/repositories/go-gitea/gitea.git", nil, 0},
+ {"https://github.com/go-gitea/gitea.git", "https", "github.com", "/go-gitea/gitea.git", nil, 0},
+ {"https://git:git@github.com/go-gitea/gitea.git", "https", "github.com", "/go-gitea/gitea.git", url.UserPassword("git", "git"), 0},
+ {"https://[fe80::14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git", "https", "[fe80::14fc:cec5:c174:d88%10]:20", "/go-gitea/gitea.git", nil, 0},
+ {"git://github.com/go-gitea/gitea.git", "git", "github.com", "/go-gitea/gitea.git", nil, 0},
+ } {
t.Run(kase.kase, func(t *testing.T) {
u, err := ParseGitURL(kase.kase)
assert.NoError(t, err)
- assert.Equal(t, kase.expected.extraMark, u.extraMark)
- assert.Equal(t, *kase.expected, *u)
+ assert.Equal(t, kase.extraMark, u.extraMark)
+ assert.Equal(t, GitURL{
+ URL: &url.URL{
+ Scheme: kase.scheme,
+ User: kase.user,
+ Host: kase.host,
+ Path: kase.path,
+ },
+ extraMark: kase.extraMark,
+ }, *u)
})
}
}
@@ -251,27 +132,18 @@ func TestMakeRepositoryBaseLink(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
- u, err := ParseRepositoryURL(t.Context(), "https://localhost:3000/subpath/user/repo.git")
- assert.NoError(t, err)
- assert.Equal(t, "/subpath/user/repo", MakeRepositoryWebLink(u))
-
- u, err = ParseRepositoryURL(t.Context(), "https://github.com/owner/repo.git")
- assert.NoError(t, err)
- assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u))
-
- u, err = ParseRepositoryURL(t.Context(), "git@github.com:owner/repo.git")
- assert.NoError(t, err)
- assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u))
-
- u, err = ParseRepositoryURL(t.Context(), "git+ssh://other:123/owner/repo.git")
- assert.NoError(t, err)
- assert.Equal(t, "https://other/owner/repo", MakeRepositoryWebLink(u))
-
- u, err = ParseRepositoryURL(t.Context(), "git+ssh://[::1]/owner/repo.git")
- assert.NoError(t, err)
- assert.Equal(t, "https://[::1]/owner/repo", MakeRepositoryWebLink(u))
-
- u, err = ParseRepositoryURL(t.Context(), "git+ssh://[::1]:2222/owner/repo.git")
- assert.NoError(t, err)
- assert.Equal(t, "https://[::1]/owner/repo", MakeRepositoryWebLink(u))
+ for _, kase := range []struct {
+ input, expected string
+ }{
+ {"https://localhost:3000/subpath/user/repo.git", "/subpath/user/repo"},
+ {"https://github.com/owner/repo.git", "https://github.com/owner/repo"},
+ {"git@github.com:owner/repo.git", "https://github.com/owner/repo"},
+ {"git+ssh://other:123/owner/repo.git", "https://other/owner/repo"},
+ {"git+ssh://[::1]/owner/repo.git", "https://[::1]/owner/repo"},
+ {"git+ssh://[::1]:2222/owner/repo.git", "https://[::1]/owner/repo"},
+ } {
+ u, err := ParseRepositoryURL(t.Context(), kase.input)
+ assert.NoError(t, err)
+ assert.Equal(t, kase.expected, MakeRepositoryWebLink(u))
+ }
}
diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go
index 5c50844e3b9..665b0e593bf 100644
--- a/modules/highlight/highlight_test.go
+++ b/modules/highlight/highlight_test.go
@@ -34,12 +34,14 @@ func TestFile(t *testing.T) {
name string
code string
want []template.HTML
+ plainWant []template.HTML
lexerName string
}{
{
name: "empty.py",
code: "",
want: lines(""),
+ plainWant: lines(""),
lexerName: "Python",
},
{
@@ -64,18 +66,21 @@ func TestFile(t *testing.T) {
name: "tags.py",
code: "<>",
want: lines(`<>`),
+ plainWant: lines("<>"),
lexerName: "Python",
},
{
name: "eol-no.py",
code: "a=1",
want: lines(`a=1`),
+ plainWant: lines(`a=1`),
lexerName: "Python",
},
{
name: "eol-newline1.py",
code: "a=1\n",
want: lines(`a=1\n`),
+ plainWant: lines(`a=1\n`),
lexerName: "Python",
},
{
@@ -86,6 +91,10 @@ func TestFile(t *testing.T) {
\n
`,
),
+ plainWant: lines(`
+a=1\n
+\n
+ `),
lexerName: "Python",
},
{
@@ -106,6 +115,13 @@ c=2
\n
c=2`,
),
+ plainWant: lines(`
+def:\n
+ a=1\n
+\n
+b=''\n
+ \n
+c=2`),
lexerName: "Python",
},
{
@@ -135,67 +151,14 @@ Content-Type: text/html
assert.Equal(t, tt.lexerName, lexerName)
})
}
-}
-
-func TestPlainText(t *testing.T) {
- tests := []struct {
- name string
- code string
- want []template.HTML
- }{
- {
- name: "empty.py",
- code: "",
- want: lines(""),
- },
- {
- name: "tags.py",
- code: "<>",
- want: lines("<>"),
- },
- {
- name: "eol-no.py",
- code: "a=1",
- want: lines(`a=1`),
- },
- {
- name: "eol-newline1.py",
- code: "a=1\n",
- want: lines(`a=1\n`),
- },
- {
- name: "eol-newline2.py",
- code: "a=1\n\n",
- want: lines(`
-a=1\n
-\n
- `),
- },
- {
- name: "empty-line-with-space.py",
- code: strings.ReplaceAll(strings.TrimSpace(`
-def:
- a=1
-
-b=''
-{space}
-c=2
- `), "{space}", " "),
- want: lines(`
-def:\n
- a=1\n
-\n
-b=''\n
- \n
-c=2`),
- },
- }
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- out := renderPlainText([]byte(tt.code))
- assert.Equal(t, tt.want, out)
- })
+ if tt.plainWant != nil {
+ t.Run("plaintext/"+tt.name, func(t *testing.T) {
+ out := renderPlainText([]byte(tt.code))
+ assert.Equal(t, tt.plainWant, out)
+ })
+ }
}
}
diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go
index 2362c20553f..1320a864a72 100644
--- a/modules/httpcache/httpcache_test.go
+++ b/modules/httpcache/httpcache_test.go
@@ -25,76 +25,58 @@ func TestHandleGenericETagCache(t *testing.T) {
respEtag string
reqHeaders map[string]string
wantHandled bool
- wantHeaders map[string]string
- wantStatus int
}
cases := []testCase{
{
name: "No If-None-Match",
wantHandled: false,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag},
},
{
name: "Mismatched If-None-Match",
reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag"`},
wantHandled: false,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag},
},
{
name: "Matched If-None-Match",
reqHeaders: map[string]string{"If-None-Match": matchedEtag},
wantHandled: true,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag},
- wantStatus: http.StatusNotModified,
},
{
name: "Multiple Mismatched If-None-Match",
reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag1", "mismatched-etag2"`},
wantHandled: false,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag},
},
{
name: "Wildcard If-None-Match matches any current representation",
reqHeaders: map[string]string{"If-None-Match": "*"},
wantHandled: true,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag},
- wantStatus: http.StatusNotModified,
},
{
name: "Weak response tag matched by a strong request tag",
respEtag: weakEtag,
reqHeaders: map[string]string{"If-None-Match": matchedEtag},
wantHandled: true,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": weakEtag},
- wantStatus: http.StatusNotModified,
},
{
name: "Weak response tag matched by a weak request tag",
respEtag: weakEtag,
reqHeaders: map[string]string{"If-None-Match": weakEtag},
wantHandled: true,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": weakEtag},
- wantStatus: http.StatusNotModified,
},
{
name: "If-Modified-Since without If-None-Match",
reqHeaders: map[string]string{"If-Modified-Since": lastModified},
wantHandled: true,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag},
- wantStatus: http.StatusNotModified,
},
{
name: "Mismatched If-None-Match takes precedence over If-Modified-Since",
reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag"`, "If-Modified-Since": lastModified},
wantHandled: false,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag},
},
{
name: "Multiple Matched If-None-Match",
reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag", ` + matchedEtag},
wantHandled: true,
- wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag},
- wantStatus: http.StatusNotModified,
},
}
@@ -107,10 +89,14 @@ func TestHandleGenericETagCache(t *testing.T) {
w := httptest.NewRecorder()
assert.Equal(t, tc.wantHandled, HandleGenericETagPrivateCache(req, w, util.IfZero(tc.respEtag, matchedEtag), lastModifiedTime))
resp := w.Result()
- for k, v := range tc.wantHeaders {
+ for k, v := range map[string]string{
+ "Last-Modified": lastModified,
+ "Cache-Control": util.Iif(tc.wantHandled, "", cacheControl),
+ "Etag": util.IfZero(tc.respEtag, matchedEtag),
+ } {
assert.Equal(t, v, resp.Header.Get(k))
}
- assert.Equal(t, tc.wantStatus, util.Iif(resp.StatusCode == http.StatusOK, 0, resp.StatusCode))
+ assert.Equal(t, util.Iif(tc.wantHandled, http.StatusNotModified, 0), util.Iif(resp.StatusCode == http.StatusOK, 0, resp.StatusCode))
})
}
}
diff --git a/modules/httplib/serve_test.go b/modules/httplib/serve_test.go
index 7f9fa2e2628..998f8efbca8 100644
--- a/modules/httplib/serve_test.go
+++ b/modules/httplib/serve_test.go
@@ -18,95 +18,51 @@ import (
"github.com/stretchr/testify/require"
)
-func TestServeUserContentByReader(t *testing.T) {
+func TestServeUserContent(t *testing.T) {
data := "0123456789abcdef"
-
- test := func(t *testing.T, expectedStatusCode int, expectedContent string) {
- _, rangeStr, _ := strings.Cut(t.Name(), "_range_")
- r := &http.Request{Header: http.Header{}, Form: url.Values{}}
- if rangeStr != "" {
- r.Header.Set("Range", "bytes="+rangeStr)
- }
- reader := strings.NewReader(data)
- w := httptest.NewRecorder()
- ServeUserContentByReader(r, w, int64(len(data)), reader, ServeHeaderOptions{})
- assert.Equal(t, expectedStatusCode, w.Code)
- if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK {
- assert.Equal(t, strconv.Itoa(len(expectedContent)), w.Header().Get("Content-Length"))
- assert.Equal(t, expectedContent, w.Body.String())
- }
+ for _, source := range []string{"reader", "file"} {
+ t.Run(source, func(t *testing.T) {
+ var tmpFile string
+ if source == "file" {
+ tmpFile = t.TempDir() + "/test"
+ assert.NoError(t, os.WriteFile(tmpFile, []byte(data), 0o644))
+ }
+ for _, tc := range []struct {
+ rangeStr string
+ expectedStatusCode int
+ expectedContent string
+ }{
+ {"", http.StatusOK, data},
+ {"0-", http.StatusPartialContent, data},
+ {"0-15", http.StatusPartialContent, data},
+ {"1-", http.StatusPartialContent, data[1:]},
+ {"1-3", http.StatusPartialContent, data[1 : 3+1]},
+ {"16-", http.StatusRequestedRangeNotSatisfiable, ""},
+ {"1-99999", http.StatusPartialContent, data[1:]},
+ } {
+ t.Run("_range_"+tc.rangeStr, func(t *testing.T) {
+ r := &http.Request{Header: http.Header{}, Form: url.Values{}}
+ if tc.rangeStr != "" {
+ r.Header.Set("Range", "bytes="+tc.rangeStr)
+ }
+ w := httptest.NewRecorder()
+ if source == "file" {
+ seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644)
+ require.NoError(t, err)
+ defer seekReader.Close()
+ ServeUserContentByFile(r, w, seekReader, ServeHeaderOptions{})
+ } else {
+ ServeUserContentByReader(r, w, int64(len(data)), strings.NewReader(data), ServeHeaderOptions{})
+ }
+ assert.Equal(t, tc.expectedStatusCode, w.Code)
+ if tc.expectedStatusCode == http.StatusPartialContent || tc.expectedStatusCode == http.StatusOK {
+ assert.Equal(t, strconv.Itoa(len(tc.expectedContent)), w.Header().Get("Content-Length"))
+ assert.Equal(t, tc.expectedContent, w.Body.String())
+ }
+ })
+ }
+ })
}
-
- t.Run("_range_", func(t *testing.T) {
- test(t, http.StatusOK, data)
- })
- t.Run("_range_0-", func(t *testing.T) {
- test(t, http.StatusPartialContent, data)
- })
- t.Run("_range_0-15", func(t *testing.T) {
- test(t, http.StatusPartialContent, data)
- })
- t.Run("_range_1-", func(t *testing.T) {
- test(t, http.StatusPartialContent, data[1:])
- })
- t.Run("_range_1-3", func(t *testing.T) {
- test(t, http.StatusPartialContent, data[1:3+1])
- })
- t.Run("_range_16-", func(t *testing.T) {
- test(t, http.StatusRequestedRangeNotSatisfiable, "")
- })
- t.Run("_range_1-99999", func(t *testing.T) {
- test(t, http.StatusPartialContent, data[1:])
- })
-}
-
-func TestServeUserContentByFile(t *testing.T) {
- data := "0123456789abcdef"
- tmpFile := t.TempDir() + "/test"
- err := os.WriteFile(tmpFile, []byte(data), 0o644)
- assert.NoError(t, err)
-
- test := func(t *testing.T, expectedStatusCode int, expectedContent string) {
- _, rangeStr, _ := strings.Cut(t.Name(), "_range_")
- r := &http.Request{Header: http.Header{}, Form: url.Values{}}
- if rangeStr != "" {
- r.Header.Set("Range", "bytes="+rangeStr)
- }
-
- seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644)
- require.NoError(t, err)
- defer seekReader.Close()
-
- w := httptest.NewRecorder()
- ServeUserContentByFile(r, w, seekReader, ServeHeaderOptions{})
- assert.Equal(t, expectedStatusCode, w.Code)
- if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK {
- assert.Equal(t, strconv.Itoa(len(expectedContent)), w.Header().Get("Content-Length"))
- assert.Equal(t, expectedContent, w.Body.String())
- }
- }
-
- t.Run("_range_", func(t *testing.T) {
- test(t, http.StatusOK, data)
- })
- t.Run("_range_0-", func(t *testing.T) {
- test(t, http.StatusPartialContent, data)
- })
- t.Run("_range_0-15", func(t *testing.T) {
- test(t, http.StatusPartialContent, data)
- })
- t.Run("_range_1-", func(t *testing.T) {
- test(t, http.StatusPartialContent, data[1:])
- })
- t.Run("_range_1-3", func(t *testing.T) {
- test(t, http.StatusPartialContent, data[1:3+1])
- })
- t.Run("_range_16-", func(t *testing.T) {
- test(t, http.StatusRequestedRangeNotSatisfiable, "")
- })
- t.Run("_range_1-99999", func(t *testing.T) {
- test(t, http.StatusPartialContent, data[1:])
- })
}
func TestServeSetHeaderContentRelated(t *testing.T) {
diff --git a/modules/indexer/issues/bleve/bleve_test.go b/modules/indexer/issues/bleve/bleve_test.go
index 2900d1f9721..079afc416c4 100644
--- a/modules/indexer/issues/bleve/bleve_test.go
+++ b/modules/indexer/issues/bleve/bleve_test.go
@@ -21,99 +21,72 @@ func TestBleveIndexer(t *testing.T) {
tests.TestIndexer(t, indexer)
}
-func TestBleveIndexerNoAssignee(t *testing.T) {
- dir := t.TempDir()
- indexer := NewIndexer(dir)
- defer indexer.Close()
-
- _, err := indexer.Init(t.Context())
- require.NoError(t, err)
-
- require.NoError(t, indexer.Index(t.Context(),
- &internal.IndexerData{ID: 1, Title: "assigned through assignee_ids", AssigneeIDs: []int64{2}},
- &internal.IndexerData{ID: 2, Title: "unassigned", NoAssignee: true},
- &internal.IndexerData{ID: 3, Title: "assigned through multiple assignee_ids", AssigneeIDs: []int64{3, 4}},
- ))
-
- testCases := []struct {
+func TestBleveIndexerSearch(t *testing.T) {
+ type searchCase struct {
name string
opts *internal.SearchOptions
expectedIDs []int64
+ }
+ for _, testCase := range []struct {
+ name string
+ data []*internal.IndexerData
+ searches []searchCase
+ checkTotal bool
}{
{
- name: "none",
- opts: &internal.SearchOptions{AssigneeID: "(none)"},
- expectedIDs: []int64{2},
+ name: "no assignee",
+ data: []*internal.IndexerData{
+ {ID: 1, Title: "assigned through assignee_ids", AssigneeIDs: []int64{2}},
+ {ID: 2, Title: "unassigned", NoAssignee: true},
+ {ID: 3, Title: "assigned through multiple assignee_ids", AssigneeIDs: []int64{3, 4}},
+ },
+ searches: []searchCase{
+ {"none", &internal.SearchOptions{AssigneeID: "(none)"}, []int64{2}},
+ {"any", &internal.SearchOptions{AssigneeID: "(any)"}, []int64{1, 3}},
+ {"specific", &internal.SearchOptions{AssigneeID: "2"}, []int64{1}},
+ {"specific first multi-assignee", &internal.SearchOptions{AssigneeID: "3"}, []int64{3}},
+ {"specific second multi-assignee", &internal.SearchOptions{AssigneeID: "4"}, []int64{3}},
+ },
+ checkTotal: true,
},
{
- name: "any",
- opts: &internal.SearchOptions{AssigneeID: "(any)"},
- expectedIDs: []int64{1, 3},
+ name: "token filter",
+ data: []*internal.IndexerData{
+ {ID: 1, Title: "fix(packages): SomeThing needs a rewrite (#12345)"},
+ {ID: 2, Title: "add support for mDNS discovery abc1234"},
+ },
+ searches: []searchCase{
+ {"exact original case", &internal.SearchOptions{Keyword: "SomeThing"}, []int64{1}},
+ {"case matching original transitions", &internal.SearchOptions{Keyword: "someThing"}, []int64{1}},
+ {"all lower case", &internal.SearchOptions{Keyword: "something"}, []int64{1}},
+ {"all upper case", &internal.SearchOptions{Keyword: "SOMETHING"}, []int64{1}},
+ {"number match", &internal.SearchOptions{Keyword: "12345"}, []int64{1}},
+ {"number as part", &internal.SearchOptions{Keyword: "1234"}, []int64{2}},
+ {"sub-word search still works", &internal.SearchOptions{Keyword: "DNS"}, []int64{2}},
+ {"sub-word search, lower case", &internal.SearchOptions{Keyword: "mdns"}, []int64{2}},
+ {"keyword is camel case", &internal.SearchOptions{Keyword: "addSupport"}, []int64{2}},
+ {"keyword not match", &internal.SearchOptions{Keyword: "addsupport"}, []int64{}},
+ },
},
- {
- name: "specific",
- opts: &internal.SearchOptions{AssigneeID: "2"},
- expectedIDs: []int64{1},
- },
- {
- name: "specific first multi-assignee",
- opts: &internal.SearchOptions{AssigneeID: "3"},
- expectedIDs: []int64{3},
- },
- {
- name: "specific second multi-assignee",
- opts: &internal.SearchOptions{AssigneeID: "4"},
- expectedIDs: []int64{3},
- },
- }
-
- for _, testCase := range testCases {
+ } {
t.Run(testCase.name, func(t *testing.T) {
- result, err := indexer.Search(t.Context(), testCase.opts)
+ indexer := NewIndexer(t.TempDir())
+ defer indexer.Close()
+
+ _, err := indexer.Init(t.Context())
require.NoError(t, err)
- assert.Equal(t, int64(len(testCase.expectedIDs)), result.Total)
- assert.ElementsMatch(t, testCase.expectedIDs, searchResultIDs(result))
- })
- }
-}
+ require.NoError(t, indexer.Index(t.Context(), testCase.data...))
-func TestBleveIndexerTokenFilter(t *testing.T) {
- dir := t.TempDir()
- indexer := NewIndexer(dir)
- defer indexer.Close()
-
- _, err := indexer.Init(t.Context())
- require.NoError(t, err)
-
- require.NoError(t, indexer.Index(t.Context(),
- &internal.IndexerData{ID: 1, Title: "fix(packages): SomeThing needs a rewrite (#12345)"},
- &internal.IndexerData{ID: 2, Title: "add support for mDNS discovery abc1234"},
- ))
-
- testCases := []struct {
- name string
- keyword string
- expectedIDs []int64
- }{
- {name: "exact original case", keyword: "SomeThing", expectedIDs: []int64{1}},
- {name: "case matching original transitions", keyword: "someThing", expectedIDs: []int64{1}},
- {name: "all lower case", keyword: "something", expectedIDs: []int64{1}},
- {name: "all upper case", keyword: "SOMETHING", expectedIDs: []int64{1}},
- {name: "number match", keyword: "12345", expectedIDs: []int64{1}},
- {name: "number as part", keyword: "1234", expectedIDs: []int64{2}},
- {name: "sub-word search still works", keyword: "DNS", expectedIDs: []int64{2}},
- {name: "sub-word search, lower case", keyword: "mdns", expectedIDs: []int64{2}},
- {name: "keyword is camel case", keyword: "addSupport", expectedIDs: []int64{2}},
- {name: "keyword not match", keyword: "addsupport", expectedIDs: []int64{}},
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.name, func(t *testing.T) {
- result, err := indexer.Search(t.Context(), &internal.SearchOptions{
- Keyword: testCase.keyword,
- })
- require.NoError(t, err)
- assert.ElementsMatch(t, testCase.expectedIDs, searchResultIDs(result))
+ for _, search := range testCase.searches {
+ t.Run(search.name, func(t *testing.T) {
+ result, err := indexer.Search(t.Context(), search.opts)
+ require.NoError(t, err)
+ if testCase.checkTotal {
+ assert.Equal(t, int64(len(search.expectedIDs)), result.Total)
+ }
+ assert.ElementsMatch(t, search.expectedIDs, searchResultIDs(result))
+ })
+ }
})
}
}
diff --git a/modules/indexer/issues/meilisearch/meilisearch_test.go b/modules/indexer/issues/meilisearch/meilisearch_test.go
index 2c9814acf6e..80cb62beb10 100644
--- a/modules/indexer/issues/meilisearch/meilisearch_test.go
+++ b/modules/indexer/issues/meilisearch/meilisearch_test.go
@@ -89,6 +89,5 @@ func TestDoubleQuoteKeyword(t *testing.T) {
assert.Empty(t, doubleQuoteKeyword(""))
assert.Equal(t, `"a" "b" "c"`, doubleQuoteKeyword("a b c"))
assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g"))
- assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g"))
assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword(`a "" "d" """g`))
}
diff --git a/modules/issue/template/template_test.go b/modules/issue/template/template_test.go
index 310e3831238..2899a23415c 100644
--- a/modules/issue/template/template_test.go
+++ b/modules/issue/template/template_test.go
@@ -15,6 +15,11 @@ import (
)
func TestValidate(t *testing.T) {
+ const metadata = `
+name: "test"
+about: "this is about"
+body:
+`
tests := []struct {
name string
filename string
@@ -44,21 +49,13 @@ about: "this is about"
},
{
name: "markdown miss value",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "markdown"
+ content: metadata + ` - type: "markdown"
`,
wantErr: "body[0](markdown): 'value' is required",
},
{
name: "markdown invalid value",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "markdown"
+ content: metadata + ` - type: "markdown"
attributes:
value: true
`,
@@ -66,11 +63,7 @@ body:
},
{
name: "markdown empty value",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "markdown"
+ content: metadata + ` - type: "markdown"
attributes:
value: ""
`,
@@ -78,33 +71,21 @@ body:
},
{
name: "textarea invalid id",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "textarea"
+ content: metadata + ` - type: "textarea"
id: "?"
`,
wantErr: "body[0](textarea): 'id' should contain only alphanumeric, '-' and '_'",
},
{
name: "textarea miss label",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "textarea"
+ content: metadata + ` - type: "textarea"
id: "1"
`,
wantErr: "body[0](textarea): 'label' is required",
},
{
name: "textarea conflict id",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "textarea"
+ content: metadata + ` - type: "textarea"
id: "1"
attributes:
label: "a"
@@ -117,11 +98,7 @@ body:
},
{
name: "textarea invalid description",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "textarea"
+ content: metadata + ` - type: "textarea"
id: "1"
attributes:
label: "a"
@@ -131,11 +108,7 @@ body:
},
{
name: "textarea invalid required",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "textarea"
+ content: metadata + ` - type: "textarea"
id: "1"
attributes:
label: "a"
@@ -146,11 +119,7 @@ body:
},
{
name: "input invalid description",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "input"
+ content: metadata + ` - type: "input"
id: "1"
attributes:
label: "a"
@@ -160,11 +129,7 @@ body:
},
{
name: "input invalid is_number",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "input"
+ content: metadata + ` - type: "input"
id: "1"
attributes:
label: "a"
@@ -175,11 +140,7 @@ body:
},
{
name: "input invalid regex",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "input"
+ content: metadata + ` - type: "input"
id: "1"
attributes:
label: "a"
@@ -190,11 +151,7 @@ body:
},
{
name: "dropdown invalid description",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "dropdown"
+ content: metadata + ` - type: "dropdown"
id: "1"
attributes:
label: "a"
@@ -204,11 +161,7 @@ body:
},
{
name: "dropdown invalid multiple",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "dropdown"
+ content: metadata + ` - type: "dropdown"
id: "1"
attributes:
label: "a"
@@ -218,11 +171,7 @@ body:
},
{
name: "dropdown invalid list",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "dropdown"
+ content: metadata + ` - type: "dropdown"
id: "1"
attributes:
label: "a"
@@ -232,11 +181,7 @@ body:
},
{
name: "checkboxes invalid description",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "checkboxes"
+ content: metadata + ` - type: "checkboxes"
id: "1"
attributes:
label: "a"
@@ -246,11 +191,7 @@ body:
},
{
name: "invalid type",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "video"
+ content: metadata + ` - type: "video"
id: "1"
attributes:
label: "a"
@@ -259,11 +200,7 @@ body:
},
{
name: "dropdown miss options",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "dropdown"
+ content: metadata + ` - type: "dropdown"
id: "1"
attributes:
label: "a"
@@ -272,11 +209,7 @@ body:
},
{
name: "dropdown invalid options",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "dropdown"
+ content: metadata + ` - type: "dropdown"
id: "1"
attributes:
label: "a"
@@ -288,11 +221,7 @@ body:
},
{
name: "checkboxes invalid options",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "checkboxes"
+ content: metadata + ` - type: "checkboxes"
id: "1"
attributes:
label: "a"
@@ -304,11 +233,7 @@ body:
},
{
name: "checkboxes option miss label",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "checkboxes"
+ content: metadata + ` - type: "checkboxes"
id: "1"
attributes:
label: "a"
@@ -319,11 +244,7 @@ body:
},
{
name: "checkboxes option invalid required",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "checkboxes"
+ content: metadata + ` - type: "checkboxes"
id: "1"
attributes:
label: "a"
@@ -335,11 +256,7 @@ body:
},
{
name: "field is required but hidden",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: "input"
+ content: metadata + ` - type: "input"
id: "1"
attributes:
label: "a"
@@ -351,11 +268,7 @@ body:
},
{
name: "checkboxes is required but hidden",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: checkboxes
+ content: metadata + ` - type: checkboxes
id: "1"
attributes:
label: Label of checkboxes
@@ -371,11 +284,7 @@ body:
},
{
name: "dropdown default is not an integer",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: dropdown
+ content: metadata + ` - type: dropdown
id: "1"
attributes:
label: Label of dropdown
@@ -393,11 +302,7 @@ body:
},
{
name: "dropdown default is out of range",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: dropdown
+ content: metadata + ` - type: dropdown
id: "1"
attributes:
label: Label of dropdown
@@ -415,11 +320,7 @@ body:
},
{
name: "dropdown without default is valid",
- content: `
-name: "test"
-about: "this is about"
-body:
- - type: dropdown
+ content: metadata + ` - type: dropdown
id: "1"
attributes:
label: Label of dropdown
@@ -770,19 +671,8 @@ Content
}
func TestRenderToMarkdown(t *testing.T) {
- type args struct {
- template string
- values url.Values
- }
- tests := []struct {
- name string
- args args
- want string
- }{
- {
- name: "normal",
- args: args{
- template: `
+ t.Run("normal", func(t *testing.T) {
+ template, err := Unmarshal("test.yaml", []byte(`
name: Name
title: Title
about: About
@@ -860,18 +750,11 @@ body:
visible: [form]
- label: Hidden Option of checkboxes
visible: [content]
-`,
- values: map[string][]string{
- "form-field-id3": {"Value of id3"},
- "form-field-id4": {"Value of id4"},
- "form-field-id5": {"0,1"},
- "form-field-id6": {"1,2"},
- "form-field-id7-0": {"on"},
- "form-field-id7-2": {"on"},
- },
- },
-
- want: `Value of the markdown shown in created issue
+`))
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := `Value of the markdown shown in created issue
### Label of textarea
@@ -894,70 +777,35 @@ Option 1 of dropdown, Option 2 of dropdown
- [ ] Option 2 of checkboxes
- [ ] Hidden Option of checkboxes
-`,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- template, err := Unmarshal("test.yaml", []byte(tt.args.template))
- if err != nil {
- t.Fatal(err)
- }
- if got := RenderToMarkdown(template, tt.args.values); got != tt.want {
- assert.Equal(t, tt.want, got)
- }
- })
- }
+`
+ if got := RenderToMarkdown(template, url.Values{
+ "form-field-id3": {"Value of id3"},
+ "form-field-id4": {"Value of id4"},
+ "form-field-id5": {"0,1"},
+ "form-field-id6": {"1,2"},
+ "form-field-id7-0": {"on"},
+ "form-field-id7-2": {"on"},
+ }); got != want {
+ assert.Equal(t, want, got)
+ }
+ })
}
func Test_minQuotes(t *testing.T) {
- type args struct {
- value string
- }
tests := []struct {
- name string
- args args
- want string
+ name string
+ value string
+ want string
}{
- {
- name: "without quote",
- args: args{
- value: "Hello\nWorld",
- },
- want: "```",
- },
- {
- name: "with 1 quote",
- args: args{
- value: "Hello\nWorld\n`text`\n",
- },
- want: "```",
- },
- {
- name: "with 3 quotes",
- args: args{
- value: "Hello\nWorld\n`text`\n```go\ntext\n```\n",
- },
- want: "````",
- },
- {
- name: "with more quotes",
- args: args{
- value: "Hello\nWorld\n`text`\n```go\ntext\n```\n``````````bash\ntext\n``````````\n",
- },
- want: "```````````",
- },
- {
- name: "not leading quotes",
- args: args{
- value: "Hello\nWorld`text````go\ntext`````````````bash\ntext``````````\n",
- },
- want: "```",
- },
+ {"without quote", "Hello\nWorld", "```"},
+ {"with 1 quote", "Hello\nWorld\n`text`\n", "```"},
+ {"with 3 quotes", "Hello\nWorld\n`text`\n```go\ntext\n```\n", "````"},
+ {"with more quotes", "Hello\nWorld\n`text`\n```go\ntext\n```\n``````````bash\ntext\n``````````\n", "```````````"},
+ {"not leading quotes", "Hello\nWorld`text````go\ntext`````````````bash\ntext``````````\n", "```"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got := minQuotes(tt.args.value)
+ got := minQuotes(tt.value)
assert.Equal(t, tt.want, got)
})
}
diff --git a/modules/lfs/http_client_test.go b/modules/lfs/http_client_test.go
index b6500cd6e96..c2998d3e272 100644
--- a/modules/lfs/http_client_test.go
+++ b/modules/lfs/http_client_test.go
@@ -162,209 +162,112 @@ func lfsTestRoundtripHandler(req *http.Request) *http.Response {
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(payload)}
}
-func TestHTTPClientDownload(t *testing.T) {
- p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}
-
- hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
- assert.Equal(t, "POST", req.Method)
- assert.Equal(t, MediaType, req.Header.Get("Content-type"))
- assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
-
- var batchRequest BatchRequest
- err := json.NewDecoder(req.Body).Decode(&batchRequest)
- assert.NoError(t, err)
-
- assert.Equal(t, "download", batchRequest.Operation)
- assert.Equal(t, &Reference{Name: "refs/heads/main"}, batchRequest.Ref)
- assert.Len(t, batchRequest.Objects, 1)
- assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid)
- assert.Equal(t, p.Size, batchRequest.Objects[0].Size)
-
- return lfsTestRoundtripHandler(req)
- })}
- dummy := &DummyTransferAdapter{}
-
- cases := []struct {
+func TestHTTPClientTransfers(t *testing.T) {
+ type clientCase struct {
endpoint string
expectedError string
+ }
+ for _, operation := range []struct {
+ name string
+ cases []clientCase
}{
{
- endpoint: "https://status-not-ok.io",
- expectedError: io.ErrUnexpectedEOF.Error(),
+ name: "download",
+ cases: []clientCase{
+ {"https://status-not-ok.io", io.ErrUnexpectedEOF.Error()},
+ {"https://invalid-json-response.io", "/(invalid json|invalid character)/"},
+ {"https://valid-batch-request-download.io", ""},
+ {"https://response-no-objects.io", ""},
+ {"https://unknown-transfer-adapter.io", "TransferAdapter not found: "},
+ {"https://error-in-response-objects.io", "Object not found"},
+ {"https://empty-actions-map.io", "missing action 'download'"},
+ {"https://download-actions-map.io", ""},
+ {"https://upload-actions-map.io", "missing action 'download'"},
+ {"https://verify-actions-map.io", "missing action 'download'"},
+ {"https://unknown-actions-map.io", "missing action 'download'"},
+ {"https://legacy-batch-request-download.io", ""},
+ },
},
{
- endpoint: "https://invalid-json-response.io",
- expectedError: "/(invalid json|invalid character)/",
+ name: "upload",
+ cases: []clientCase{
+ {"https://status-not-ok.io", io.ErrUnexpectedEOF.Error()},
+ {"https://invalid-json-response.io", "/(invalid json|invalid character)/"},
+ {"https://valid-batch-request-upload.io", ""},
+ {"https://response-no-objects.io", ""},
+ {"https://unknown-transfer-adapter.io", "TransferAdapter not found: "},
+ {"https://error-in-response-objects.io", "Object not found"},
+ {"https://empty-actions-map.io", ""},
+ {"https://download-actions-map.io", "missing action 'upload'"},
+ {"https://upload-actions-map.io", ""},
+ {"https://verify-actions-map.io", "missing action 'upload'"},
+ {"https://unknown-actions-map.io", "missing action 'upload'"},
+ },
},
- {
- endpoint: "https://valid-batch-request-download.io",
- expectedError: "",
- },
- {
- endpoint: "https://response-no-objects.io",
- expectedError: "",
- },
- {
- endpoint: "https://unknown-transfer-adapter.io",
- expectedError: "TransferAdapter not found: ",
- },
- {
- endpoint: "https://error-in-response-objects.io",
- expectedError: "Object not found",
- },
- {
- endpoint: "https://empty-actions-map.io",
- expectedError: "missing action 'download'",
- },
- {
- endpoint: "https://download-actions-map.io",
- expectedError: "",
- },
- {
- endpoint: "https://upload-actions-map.io",
- expectedError: "missing action 'download'",
- },
- {
- endpoint: "https://verify-actions-map.io",
- expectedError: "missing action 'download'",
- },
- {
- endpoint: "https://unknown-actions-map.io",
- expectedError: "missing action 'download'",
- },
- {
- endpoint: "https://legacy-batch-request-download.io",
- expectedError: "",
- },
- }
+ } {
+ t.Run(operation.name, func(t *testing.T) {
+ p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}
- defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)()
- for _, c := range cases {
- t.Run(c.endpoint, func(t *testing.T) {
- client := &HTTPClient{
- client: hc,
- endpoint: c.endpoint,
- transfers: map[string]TransferAdapter{
- "dummy": dummy,
- },
- }
+ hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
+ assert.Equal(t, "POST", req.Method)
+ assert.Equal(t, MediaType, req.Header.Get("Content-type"))
+ assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
- err := client.Download(t.Context(), "refs/heads/main", []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error {
- if objectError != nil {
- return objectError
- }
- b, err := io.ReadAll(content)
+ var batchRequest BatchRequest
+ err := json.NewDecoder(req.Body).Decode(&batchRequest)
assert.NoError(t, err)
- assert.Equal(t, []byte("dummy"), b)
- return nil
- })
- if c.expectedError != "" {
- if strings.HasPrefix(c.expectedError, "/") && strings.HasSuffix(c.expectedError, "/") {
- assert.Regexp(t, strings.Trim(c.expectedError, "/"), err.Error())
+
+ assert.Equal(t, operation.name, batchRequest.Operation)
+ if operation.name == "download" {
+ assert.Equal(t, &Reference{Name: "refs/heads/main"}, batchRequest.Ref)
} else {
- assert.ErrorContains(t, err, c.expectedError)
+ assert.Nil(t, batchRequest.Ref)
}
- } else {
- assert.NoError(t, err)
- }
- })
- }
-}
-
-func TestHTTPClientUpload(t *testing.T) {
- p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}
-
- hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
- assert.Equal(t, "POST", req.Method)
- assert.Equal(t, MediaType, req.Header.Get("Content-type"))
- assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
-
- var batchRequest BatchRequest
- err := json.NewDecoder(req.Body).Decode(&batchRequest)
- assert.NoError(t, err)
-
- assert.Equal(t, "upload", batchRequest.Operation)
- assert.Nil(t, batchRequest.Ref)
- assert.Len(t, batchRequest.Objects, 1)
- assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid)
- assert.Equal(t, p.Size, batchRequest.Objects[0].Size)
-
- return lfsTestRoundtripHandler(req)
- })}
- dummy := &DummyTransferAdapter{}
-
- cases := []struct {
- endpoint string
- expectedError string
- }{
- {
- endpoint: "https://status-not-ok.io",
- expectedError: io.ErrUnexpectedEOF.Error(),
- },
- {
- endpoint: "https://invalid-json-response.io",
- expectedError: "/(invalid json|invalid character)/",
- },
- {
- endpoint: "https://valid-batch-request-upload.io",
- expectedError: "",
- },
- {
- endpoint: "https://response-no-objects.io",
- expectedError: "",
- },
- {
- endpoint: "https://unknown-transfer-adapter.io",
- expectedError: "TransferAdapter not found: ",
- },
- {
- endpoint: "https://error-in-response-objects.io",
- expectedError: "Object not found",
- },
- {
- endpoint: "https://empty-actions-map.io",
- expectedError: "",
- },
- {
- endpoint: "https://download-actions-map.io",
- expectedError: "missing action 'upload'",
- },
- {
- endpoint: "https://upload-actions-map.io",
- expectedError: "",
- },
- {
- endpoint: "https://verify-actions-map.io",
- expectedError: "missing action 'upload'",
- },
- {
- endpoint: "https://unknown-actions-map.io",
- expectedError: "missing action 'upload'",
- },
- }
-
- defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)()
- for _, c := range cases {
- t.Run(c.endpoint, func(t *testing.T) {
- client := &HTTPClient{
- client: hc,
- endpoint: c.endpoint,
- transfers: map[string]TransferAdapter{
- "dummy": dummy,
- },
- }
-
- err := client.Upload(t.Context(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) {
- return io.NopCloser(new(bytes.Buffer)), objectError
- })
- if c.expectedError != "" {
- if strings.HasPrefix(c.expectedError, "/") && strings.HasSuffix(c.expectedError, "/") {
- assert.Regexp(t, strings.Trim(c.expectedError, "/"), err.Error())
- } else {
- assert.ErrorContains(t, err, c.expectedError)
- }
- } else {
- assert.NoError(t, err)
+ assert.Len(t, batchRequest.Objects, 1)
+ assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid)
+ assert.Equal(t, p.Size, batchRequest.Objects[0].Size)
+
+ return lfsTestRoundtripHandler(req)
+ })}
+ dummy := &DummyTransferAdapter{}
+
+ defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)()
+ for _, testCase := range operation.cases {
+ t.Run(testCase.endpoint, func(t *testing.T) {
+ client := &HTTPClient{
+ client: hc,
+ endpoint: testCase.endpoint,
+ transfers: map[string]TransferAdapter{
+ "dummy": dummy,
+ },
+ }
+
+ var err error
+ if operation.name == "download" {
+ err = client.Download(t.Context(), "refs/heads/main", []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error {
+ if objectError != nil {
+ return objectError
+ }
+ b, err := io.ReadAll(content)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("dummy"), b)
+ return nil
+ })
+ } else {
+ err = client.Upload(t.Context(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) {
+ return io.NopCloser(new(bytes.Buffer)), objectError
+ })
+ }
+ if testCase.expectedError != "" {
+ if strings.HasPrefix(testCase.expectedError, "/") && strings.HasSuffix(testCase.expectedError, "/") {
+ assert.Regexp(t, strings.Trim(testCase.expectedError, "/"), err.Error())
+ } else {
+ assert.ErrorContains(t, err, testCase.expectedError)
+ }
+ } else {
+ assert.NoError(t, err)
+ }
+ })
}
})
}
diff --git a/modules/lfs/transferadapter_test.go b/modules/lfs/transferadapter_test.go
index b3f6422a782..8eea459e83d 100644
--- a/modules/lfs/transferadapter_test.go
+++ b/modules/lfs/transferadapter_test.go
@@ -69,7 +69,7 @@ func TestBasicTransferAdapter(t *testing.T) {
hc := &http.Client{Transport: RoundTripFunc(roundTripHandler)}
a := &BasicTransferAdapter{hc}
- t.Run("Download", func(t *testing.T) {
+ testOperation := func(t *testing.T, endpoint string, transfer func(*Link) error) {
cases := []struct {
link *Link
expectederror string
@@ -77,7 +77,7 @@ func TestBasicTransferAdapter(t *testing.T) {
// case 0
{
link: &Link{
- Href: "https://download-request.io",
+ Href: endpoint,
Header: map[string]string{"test-header": "test-value"},
},
expectederror: "",
@@ -93,46 +93,26 @@ func TestBasicTransferAdapter(t *testing.T) {
}
for n, c := range cases {
- _, err := a.Download(t.Context(), c.link)
+ err := transfer(c.link)
if len(c.expectederror) > 0 {
assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
} else {
assert.NoError(t, err, "case %d", n)
}
}
+ }
+
+ t.Run("Download", func(t *testing.T) {
+ testOperation(t, "https://download-request.io", func(link *Link) error {
+ _, err := a.Download(t.Context(), link)
+ return err
+ })
})
t.Run("Upload", func(t *testing.T) {
- cases := []struct {
- link *Link
- expectederror string
- }{
- // case 0
- {
- link: &Link{
- Href: "https://upload-request.io",
- Header: map[string]string{"test-header": "test-value"},
- },
- expectederror: "",
- },
- // case 1
- {
- link: &Link{
- Href: "https://error-response.io",
- Header: map[string]string{"test-header": "test-value"},
- },
- expectederror: "Object not found",
- },
- }
-
- for n, c := range cases {
- err := a.Upload(t.Context(), c.link, p, strings.NewReader("dummy"))
- if len(c.expectederror) > 0 {
- assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
- } else {
- assert.NoError(t, err, "case %d", n)
- }
- }
+ testOperation(t, "https://upload-request.io", func(link *Link) error {
+ return a.Upload(t.Context(), link, p, strings.NewReader("dummy"))
+ })
})
t.Run("Upload created", func(t *testing.T) {
@@ -145,35 +125,8 @@ func TestBasicTransferAdapter(t *testing.T) {
})
t.Run("Verify", func(t *testing.T) {
- cases := []struct {
- link *Link
- expectederror string
- }{
- // case 0
- {
- link: &Link{
- Href: "https://verify-request.io",
- Header: map[string]string{"test-header": "test-value"},
- },
- expectederror: "",
- },
- // case 1
- {
- link: &Link{
- Href: "https://error-response.io",
- Header: map[string]string{"test-header": "test-value"},
- },
- expectederror: "Object not found",
- },
- }
-
- for n, c := range cases {
- err := a.Verify(t.Context(), c.link, p)
- if len(c.expectederror) > 0 {
- assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
- } else {
- assert.NoError(t, err, "case %d", n)
- }
- }
+ testOperation(t, "https://verify-request.io", func(link *Link) error {
+ return a.Verify(t.Context(), link, p)
+ })
})
}
diff --git a/modules/log/event_format_test.go b/modules/log/event_format_test.go
index 6fd0f36d485..f7e38234144 100644
--- a/modules/log/event_format_test.go
+++ b/modules/log/event_format_test.go
@@ -11,45 +11,33 @@ import (
)
func TestItoa(t *testing.T) {
- b := itoa(nil, 0, 0)
- assert.Equal(t, "0", string(b))
-
- b = itoa(nil, 0, 1)
- assert.Equal(t, "0", string(b))
-
- b = itoa(nil, 0, 2)
- assert.Equal(t, "00", string(b))
+ for width, expected := range []string{"0", "0", "00"} {
+ assert.Equal(t, expected, string(itoa(nil, 0, width)))
+ }
}
func TestEventFormatTextMessage(t *testing.T) {
- res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: false, Flags: Flags{defined: true, flags: 0xffffffff}},
- &Event{
- Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC),
- Caller: "caller",
- Filename: "filename",
- Line: 123,
- Level: ERROR,
- Stacktrace: "stacktrace",
- },
- "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue),
- )
-
- assert.Equal(t, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [no-gopid] msg format: arg0 arg1
+ for _, testCase := range []struct {
+ colorize bool
+ expected string
+ }{
+ {false, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [no-gopid] msg format: arg0 arg1
stacktrace
-`, string(res))
-
- res = EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: true, Flags: Flags{defined: true, flags: 0xffffffff}},
- &Event{
- Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC),
- Caller: "caller",
- Filename: "filename",
- Line: 123,
- Level: ERROR,
- Stacktrace: "stacktrace",
- },
- "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue),
- )
-
- assert.Equal(t, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mno-gopid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res))
+`},
+ {true, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mno-gopid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n"},
+ } {
+ res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: testCase.colorize, Flags: Flags{defined: true, flags: 0xffffffff}},
+ &Event{
+ Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC),
+ Caller: "caller",
+ Filename: "filename",
+ Line: 123,
+ Level: ERROR,
+ Stacktrace: "stacktrace",
+ },
+ "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue),
+ )
+ assert.Equal(t, testCase.expected, string(res))
+ }
}
diff --git a/modules/log/level_test.go b/modules/log/level_test.go
index 8dc96ecfaeb..fdee7f6abd3 100644
--- a/modules/log/level_test.go
+++ b/modules/log/level_test.go
@@ -24,25 +24,17 @@ func TestLevelMarshalUnmarshalJSON(t *testing.T) {
assert.Equal(t, string(makeTestLevelBytes(INFO.String())), string(levelBytes))
var testLevel testLevel
- err = json.Unmarshal(levelBytes, &testLevel)
- assert.NoError(t, err)
- assert.Equal(t, INFO, testLevel.Level)
-
- err = json.Unmarshal(makeTestLevelBytes(`FOFOO`), &testLevel)
- assert.NoError(t, err)
- assert.Equal(t, INFO, testLevel.Level)
-
- err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 2), &testLevel)
- assert.NoError(t, err)
- assert.Equal(t, INFO, testLevel.Level)
-
- err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 10012), &testLevel)
- assert.NoError(t, err)
- assert.Equal(t, INFO, testLevel.Level)
-
- err = json.Unmarshal([]byte(`{"level":{}}`), &testLevel)
- assert.NoError(t, err)
- assert.Equal(t, INFO, testLevel.Level)
+ for _, levelJSON := range [][]byte{
+ levelBytes,
+ makeTestLevelBytes(`FOFOO`),
+ fmt.Appendf(nil, `{"level":%d}`, 2),
+ fmt.Appendf(nil, `{"level":%d}`, 10012),
+ []byte(`{"level":{}}`),
+ } {
+ err = json.Unmarshal(levelJSON, &testLevel)
+ assert.NoError(t, err)
+ assert.Equal(t, INFO, testLevel.Level)
+ }
assert.Equal(t, INFO.String(), Level(1001).String())
diff --git a/modules/markup/camo_test.go b/modules/markup/camo_test.go
index d79b78605fc..0dba7b312d3 100644
--- a/modules/markup/camo_test.go
+++ b/modules/markup/camo_test.go
@@ -21,27 +21,18 @@ func TestCamoHandleLink(t *testing.T) {
setting.Camo.ServerURL = "https://image.proxy"
setting.Camo.HMACKey = "geheim"
dataURL := "data:image/png;base64,i//V"
- assert.Equal(t, dataURL, camoHandleLink(dataURL))
-
- assert.Equal(t,
- "https://gitea.com/img.jpg",
- camoHandleLink("https://gitea.com/img.jpg"))
- assert.Equal(t,
- "https://testimages.org/img.jpg",
- camoHandleLink("https://testimages.org/img.jpg"))
- assert.Equal(t,
- "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc",
- camoHandleLink("http://testimages.org/img.jpg"))
+ test := func(expectedHTTPS string) {
+ assert.Equal(t, dataURL, camoHandleLink(dataURL))
+ assert.Equal(t,
+ "https://gitea.com/img.jpg",
+ camoHandleLink("https://gitea.com/img.jpg"))
+ assert.Equal(t, expectedHTTPS, camoHandleLink("https://testimages.org/img.jpg"))
+ assert.Equal(t,
+ "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc",
+ camoHandleLink("http://testimages.org/img.jpg"))
+ }
+ test("https://testimages.org/img.jpg")
setting.Camo.Always = true
- assert.Equal(t, dataURL, camoHandleLink(dataURL))
- assert.Equal(t,
- "https://gitea.com/img.jpg",
- camoHandleLink("https://gitea.com/img.jpg"))
- assert.Equal(t,
- "https://image.proxy/tkdlvmqpbIr7SjONfHNgEU622y0/aHR0cHM6Ly90ZXN0aW1hZ2VzLm9yZy9pbWcuanBn",
- camoHandleLink("https://testimages.org/img.jpg"))
- assert.Equal(t,
- "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc",
- camoHandleLink("http://testimages.org/img.jpg"))
+ test("https://image.proxy/tkdlvmqpbIr7SjONfHNgEU622y0/aHR0cHM6Ly90ZXN0aW1hZ2VzLm9yZy9pbWcuanBn")
}
diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go
index f0366f68275..6c0f52c879a 100644
--- a/modules/markup/html_internal_test.go
+++ b/modules/markup/html_internal_test.go
@@ -85,7 +85,6 @@ func TestRender_IssueIndexPattern(t *testing.T) {
test("#abcd")
test("test#1234")
test("#1234test")
- test("#abcd")
test("test!1234")
test("!1234test")
test(" test !1234test")
@@ -257,16 +256,7 @@ func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) {
testRenderIssueIndexPattern(t, "#1", "#1", NewTestRenderContext(metas))
testRenderIssueIndexPattern(t, "#1312", "#1312", NewTestRenderContext(metas))
testRenderIssueIndexPattern(t, "!1", "!1", NewTestRenderContext(metas))
-}
-func TestRender_PostProcessIssueTitle(t *testing.T) {
- setting.AppURL = TestAppURL
- metas := map[string]string{
- "format": "https://someurl.com/{user}/{repo}/{index}",
- "user": "someUser",
- "repo": "someRepo",
- "style": IssueNameStyleNumeric,
- }
actual := PostProcessIssueTitle(NewTestRenderContext(metas), "#1")
assert.Equal(t, template.HTML("#1"), actual)
}
diff --git a/modules/markup/html_issue_test.go b/modules/markup/html_issue_test.go
index 210a699051d..6ed0df082be 100644
--- a/modules/markup/html_issue_test.go
+++ b/modules/markup/html_issue_test.go
@@ -37,43 +37,24 @@ func TestRender_IssueList(t *testing.T) {
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out)))
}
- t.Run("NormalIssueRef", func(t *testing.T) {
- test(
- "#12345",
+ for _, tc := range []struct {
+ name, input, expected string
+ }{
+ {
+ "NormalIssueRef", "#12345",
`#12345
`,
- )
- })
-
- t.Run("ListIssueRef", func(t *testing.T) {
- test(
- "* #12345",
- `
+ },
+ {"ListIssueRef", "* #12345", ``,
- )
- })
-
- t.Run("ListIssueRefNormal", func(t *testing.T) {
- test(
- "* foo #12345 bar",
- ``},
+ {"ListIssueRefNormal", "* foo #12345 bar", ``,
- )
- })
-
- t.Run("ListTodoIssueRef", func(t *testing.T) {
- test(
- "* [ ] #12345",
- ``},
+ {"ListTodoIssueRef", "* [ ] #12345", ``,
- )
- })
-
- t.Run("IssueFootnote", func(t *testing.T) {
- test(
- "foo[^1][^2]\n\n[^1]: bar\n[^2]: baz",
+
`},
+ {
+ "IssueFootnote", "foo[^1][^2]\n\n[^1]: bar\n[^2]: baz",
`foo1 2
@@ -86,6 +67,10 @@ func TestRender_IssueList(t *testing.T) {
`,
- )
- })
+ },
+ } {
+ t.Run(tc.name, func(_t *testing.T) {
+ test(tc.input, tc.expected)
+ })
+ }
}
diff --git a/modules/markup/html_node_test.go b/modules/markup/html_node_test.go
index 007e3c2a122..f5751e4ad1a 100644
--- a/modules/markup/html_node_test.go
+++ b/modules/markup/html_node_test.go
@@ -11,94 +11,79 @@ import (
)
func TestProcessNodeAttrID_HTMLHeadingWithoutID(t *testing.T) {
- // Test that HTML headings without id get an auto-generated id from their text content
- // when EnableHeadingIDGeneration is true (for repo files and wiki pages)
testCases := []struct {
- name string
- input string
- expected string
+ name string
+ generateHeadingID bool
+ input, expected string
}{
+ // Test that HTML headings without id get an auto-generated id from their text content
+ // when EnableHeadingIDGeneration is true (for repo files and wiki pages)
{
- name: "h1 without id",
- input: `Heading without ID
`,
- expected: `Heading without ID
`,
+ "h1 without id", true,
+ `Heading without ID
`,
+ `Heading without ID
`,
},
{
- name: "h2 without id",
- input: `Another Heading
`,
- expected: `Another Heading
`,
+ "h2 without id", true,
+ `Another Heading
`,
+ `Another Heading
`,
},
{
- name: "h3 without id",
- input: `Third Level
`,
- expected: `Third Level
`,
+ "h3 without id", true,
+ `Third Level
`,
+ `Third Level
`,
},
{
- name: "h1 with existing id should keep it",
- input: `Heading with ID
`,
- expected: `Heading with ID
`,
+ "h1 with existing id should keep it", true,
+ `Heading with ID
`,
+ `Heading with ID
`,
},
{
- name: "h1 with user-content prefix should not double prefix",
- input: `Already Prefixed
`,
- expected: `Already Prefixed
`,
+ "h1 with user-content prefix should not double prefix", true,
+ `Already Prefixed
`,
+ `Already Prefixed
`,
},
{
- name: "heading with special characters",
- input: `What is Wine Staging?
`,
- expected: `What is Wine Staging?
`,
+ "heading with special characters", true,
+ `What is Wine Staging?
`,
+ `What is Wine Staging?
`,
},
{
- name: "heading with nested elements",
- input: `Bold and Italic
`,
- expected: `Bold and Italic
`,
+ "heading with nested elements", true,
+ `Bold and Italic
`,
+ `Bold and Italic
`,
+ },
+ // Test that HTML headings in comment-like contexts (issue comments)
+ // do NOT get auto-generated IDs to avoid duplicate IDs on pages with multiple documents.
+ // This is controlled by EnableHeadingIDGeneration which defaults to false.
+ // Default context without EnableHeadingIDGeneration (simulates comment rendering)
+ {
+ "h1 without id in comment context", false,
+ `Heading without ID
`,
+ `Heading without ID
`,
+ },
+ {
+ "h2 without id in comment context", false,
+ `Another Heading
`,
+ `Another Heading
`,
+ },
+ {
+ "h1 with existing id should still be prefixed", false,
+ `Heading with ID
`,
+ `Heading with ID
`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var result strings.Builder
- ctx := NewTestRenderContext().WithEnableHeadingIDGeneration(true)
+ ctx := NewTestRenderContext()
+ if tc.generateHeadingID {
+ ctx.WithEnableHeadingIDGeneration(true)
+ }
err := PostProcessDefault(ctx, strings.NewReader(tc.input), &result)
assert.NoError(t, err)
assert.Equal(t, tc.expected, strings.TrimSpace(result.String()))
})
}
}
-
-func TestProcessNodeAttrID_SkipHeadingIDForComments(t *testing.T) {
- // Test that HTML headings in comment-like contexts (issue comments)
- // do NOT get auto-generated IDs to avoid duplicate IDs on pages with multiple documents.
- // This is controlled by EnableHeadingIDGeneration which defaults to false.
- testCases := []struct {
- name string
- input string
- expected string
- }{
- {
- name: "h1 without id in comment context",
- input: `Heading without ID
`,
- expected: `Heading without ID
`,
- },
- {
- name: "h2 without id in comment context",
- input: `Another Heading
`,
- expected: `Another Heading
`,
- },
- {
- name: "h1 with existing id should still be prefixed",
- input: `Heading with ID
`,
- expected: `Heading with ID
`,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- var result strings.Builder
- // Default context without EnableHeadingIDGeneration (simulates comment rendering)
- err := PostProcessDefault(NewTestRenderContext(), strings.NewReader(tc.input), &result)
- assert.NoError(t, err)
- assert.Equal(t, tc.expected, strings.TrimSpace(result.String()))
- })
- }
-}
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 5b87b1a2e2d..66456c88977 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -145,90 +145,42 @@ func TestRender_links(t *testing.T) {
setting.Markdown.CustomURLSchemes = []string{"ftp", "magnet"}
common.InitLinkURLSchemes(setting.Markdown.CustomURLSchemes)
- // Text that should be turned into URL
- test(
- "https://www.example.com",
- `https://www.example.com
`)
- test(
- "http://www.example.com",
- `http://www.example.com
`)
- test(
- "https://example.com",
- `https://example.com
`)
- test(
- "http://example.com",
- `http://example.com
`)
- test(
- "http://foo.com/blah_blah",
- `http://foo.com/blah_blah
`)
- test(
- "http://foo.com/blah_blah/",
- `http://foo.com/blah_blah/
`)
- test(
- "http://www.example.com/wpstyle/?p=364",
- `http://www.example.com/wpstyle/?p=364
`)
- test(
- "https://www.example.com/foo/?bar=baz&inga=42&quux",
- `https://www.example.com/foo/?bar=baz&inga=42&quux
`)
- test(
- "http://142.42.1.1/",
- `http://142.42.1.1/
`)
- test(
- "https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd",
- `https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd
`)
- test(
- "https://en.wikipedia.org/wiki/URL_(disambiguation)",
- `https://en.wikipedia.org/wiki/URL_(disambiguation)
`)
- test(
- "https://foo_bar.example.com/",
- `https://foo_bar.example.com/
`)
- test(
- "https://stackoverflow.com/questions/2896191/what-is-go-used-fore",
- `https://stackoverflow.com/questions/2896191/what-is-go-used-fore
`)
- test(
- "https://username:password@gitea.com",
- `https://username:password@gitea.com
`)
- test(
- "ftp://gitea.com/file.txt",
- `ftp://gitea.com/file.txt
`)
- test(
- "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download",
- `magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download
`)
- test(
- `[link](https://example.com)`,
- `link
`)
- test(
- `[link](mailto:test@example.com)`,
- `link
`)
- test(
- `[link](javascript:xss)`,
- `link
`)
+ for _, testCase := range []struct {
+ input, expected string
+ }{
+ // Text that should be turned into URL
+ {"https://www.example.com", `https://www.example.com
`},
+ {"http://www.example.com", `http://www.example.com
`},
+ {"https://example.com", `https://example.com
`},
+ {"http://example.com", `http://example.com
`},
+ {"http://foo.com/blah_blah", `http://foo.com/blah_blah
`},
+ {"http://foo.com/blah_blah/", `http://foo.com/blah_blah/
`},
+ {"http://www.example.com/wpstyle/?p=364", `http://www.example.com/wpstyle/?p=364
`},
+ {"https://www.example.com/foo/?bar=baz&inga=42&quux", `https://www.example.com/foo/?bar=baz&inga=42&quux
`},
+ {"http://142.42.1.1/", `http://142.42.1.1/
`},
+ {"https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd", `https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd
`},
+ {"https://en.wikipedia.org/wiki/URL_(disambiguation)", `https://en.wikipedia.org/wiki/URL_(disambiguation)
`},
+ {"https://foo_bar.example.com/", `https://foo_bar.example.com/
`},
+ {"https://stackoverflow.com/questions/2896191/what-is-go-used-fore", `https://stackoverflow.com/questions/2896191/what-is-go-used-fore
`},
+ {"https://username:password@gitea.com", `https://username:password@gitea.com
`},
+ {"ftp://gitea.com/file.txt", `ftp://gitea.com/file.txt
`},
+ {"magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download", `magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download
`},
+ {`[link](https://example.com)`, `link
`},
+ {`[link](mailto:test@example.com)`, `link
`},
+ {`[link](javascript:xss)`, `link
`},
- // Test that should *not* be turned into URL
- test(
- "www.example.com",
- `www.example.com
`)
- test(
- "example.com",
- `example.com
`)
- test(
- "test.example.com",
- `test.example.com
`)
- test(
- "http://",
- `http://
`)
- test(
- "https://",
- `https://
`)
- test(
- "://",
- `://
`)
- test(
- "www",
- `www
`)
- test(
- "ftps://gitea.com",
- `ftps://gitea.com
`)
+ // Test that should *not* be turned into URL
+ {"www.example.com", `www.example.com
`},
+ {"example.com", `example.com
`},
+ {"test.example.com", `test.example.com
`},
+ {"http://", `http://
`},
+ {"https://", `https://
`},
+ {"://", `://
`},
+ {"www", `www
`},
+ {"ftps://gitea.com", `ftps://gitea.com
`},
+ } {
+ test(testCase.input, testCase.expected)
+ }
t.Run("LinkEllipsis", func(t *testing.T) {
input := util.EllipsisDisplayString("http://10.1.2.3", 12)
@@ -244,73 +196,41 @@ func TestRender_links(t *testing.T) {
func TestRender_email(t *testing.T) {
setting.AppURL = markup.TestAppURL
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
- test := func(input, expected string) {
- res, err := testRenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input)
- assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res), "input: %s", input)
- }
+ for _, testCase := range []struct {
+ input, expected string
+ }{
+ // Text that should be turned into email link
+ {"info@gitea.com", `info@gitea.com
`},
+ {"(info@gitea.com)", `(info@gitea.com)
`},
+ {"[info@gitea.com]", `[info@gitea.com]
`},
+ {"info@gitea.com.", `info@gitea.com.
`},
+ {"firstname+lastname@gitea.com", `firstname+lastname@gitea.com
`},
+ {"send email to info@gitea.co.uk.", `send email to info@gitea.co.uk.
`},
- // Text that should be turned into email link
- test(
- "info@gitea.com",
- `info@gitea.com
`)
- test(
- "(info@gitea.com)",
- `(info@gitea.com)
`)
- test(
- "[info@gitea.com]",
- `[info@gitea.com]
`)
- test(
- "info@gitea.com.",
- `info@gitea.com.
`)
- test(
- "firstname+lastname@gitea.com",
- `firstname+lastname@gitea.com
`)
- test(
- "send email to info@gitea.co.uk.",
- `send email to info@gitea.co.uk.
`)
-
- test(
- `j.doe@example.com,
+ {`j.doe@example.com,
j.doe@example.com.
j.doe@example.com;
j.doe@example.com?
- j.doe@example.com!`,
- `j.doe@example.com,
+ j.doe@example.com!`, `
j.doe@example.com,
j.doe@example.com.
j.doe@example.com;
j.doe@example.com?
-j.doe@example.com!
`)
+j.doe@example.com!`},
- // match GitHub behavior
- test("email@domain@domain.com", `email@domain@domain.com
`)
+ // match GitHub behavior
+ {"email@domain@domain.com", `email@domain@domain.com
`},
- // match GitHub behavior
- test(`"info@gitea.com"`, `"info@gitea.com"
`)
+ // match GitHub behavior
+ {`"info@gitea.com"`, `"info@gitea.com"
`},
- // Test that should *not* be turned into email links
- test(
- "/home/gitea/mailstore/info@gitea/com",
- `/home/gitea/mailstore/info@gitea/com
`)
- test(
- "git@try.gitea.io:go-gitea/gitea.git",
- `git@try.gitea.io:go-gitea/gitea.git
`)
- test(
- "https://foo:bar@gitea.io",
- `https://foo:bar@gitea.io
`)
- test(
- "gitea@3",
- `gitea@3
`)
- test(
- "gitea@gmail.c",
- `gitea@gmail.c
`)
- test(
- "email@domain..com",
- `email@domain..com
`)
+ // Test that should *not* be turned into email links
+ {"/home/gitea/mailstore/info@gitea/com", `/home/gitea/mailstore/info@gitea/com
`},
+ {"git@try.gitea.io:go-gitea/gitea.git", `git@try.gitea.io:go-gitea/gitea.git
`},
+ {"https://foo:bar@gitea.io", `https://foo:bar@gitea.io
`},
+ {"gitea@3", `gitea@3
`},
+ {"gitea@gmail.c", `gitea@gmail.c
`},
+ {"email@domain..com", `email@domain..com
`},
- cases := []struct {
- input, expected string
- }{
// match GitHub behavior
{"?a@d.zz", `?a@d.zz
`},
{"*a@d.zz", `*a@d.zz
`},
@@ -320,9 +240,10 @@ func TestRender_email(t *testing.T) {
// maybe we should reduce the candidate characters for the "name" part in the future
{"a*a@d.zz", `a*a@d.zz
`},
{"a~a@d.zz", `a~a@d.zz
`},
- }
- for _, c := range cases {
- test(c.input, c.expected)
+ } {
+ res, err := testRenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), testCase.input)
+ assert.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(testCase.expected), strings.TrimSpace(res), "input: %s", testCase.input)
}
}
@@ -394,12 +315,6 @@ func TestRender_ShortLinks(t *testing.T) {
setting.AppURL = markup.TestAppURL
tree := markup.TestRepoURL + "src/master"
- test := func(input, expected string) {
- buffer, err := markdown.RenderString(markup.NewTestRenderContext(tree), input)
- assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
- }
-
url := tree + "/Link"
otherURL := tree + "/Other-Link"
encodedURL := tree + "/Link%3F"
@@ -410,88 +325,36 @@ func TestRender_ShortLinks(t *testing.T) {
renderableFileURL := tree + "/markdown_file.md"
unrenderableFileURL := tree + "/file.zip"
favicon := "http://google.com/favicon.ico"
- test(
- "[[Link]]",
- `Link
`,
- )
- test(
- "[[Link.-]]",
- `Link.-
`,
- )
- test(
- "[[Link.jpg]]",
- `
`,
- )
- test(
- "[["+favicon+"]]",
- `
`,
- )
- test(
- "[[Name|Link]]",
- `Name
`,
- )
- test(
- "[[Name|Link.jpg]]",
- `
`,
- )
- test(
- "[[Name|Link.jpg|alt=AltName]]",
- `
`,
- )
- test(
- "[[Name|Link.jpg|title=Title]]",
- `
`,
- )
- test(
- "[[Name|Link.jpg|alt=AltName|title=Title]]",
- `
`,
- )
- test(
- "[[Name|Link.jpg|alt=\"AltName\"|title='Title']]",
- `
`,
- )
- test(
- "[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]",
- `
`,
- )
- test(
- "[[Link]] [[Other Link]]",
- `Link Other Link
`,
- )
- test(
- "[[Link?]]",
- `Link?
`,
- )
- test(
- "[[Link]] [[Other Link]] [[Link?]]",
- `Link Other Link Link?
`,
- )
- test(
- "[[markdown_file.md]]",
- `markdown_file.md
`,
- )
- test(
- "[[file.zip]]",
- `file.zip
`,
- )
- test(
- "[[Link #.jpg]]",
- `
`,
- )
- test(
- "[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]",
- `
`,
- )
- // FIXME: it's unable to resolve: [[link?k=v]]
- // FIXME: it is a wrong test case, it is not an image, but a link with anchor "#.jpg"
- test(
- "[[some/path/Link #.jpg]]",
- `
`,
- )
- test(
- "[[foobar]]
",
- `[[foobar]]
`,
- )
+ for _, testCase := range []struct {
+ input, expected string
+ }{
+ {"[[Link]]", `Link
`},
+ {"[[Link.-]]", `Link.-
`},
+ {"[[Link.jpg]]", `
`},
+ {"[[" + favicon + "]]", `
`},
+ {"[[Name|Link]]", `Name
`},
+ {"[[Name|Link.jpg]]", `
`},
+ {"[[Name|Link.jpg|alt=AltName]]", `
`},
+ {"[[Name|Link.jpg|title=Title]]", `
`},
+ {"[[Name|Link.jpg|alt=AltName|title=Title]]", `
`},
+ {"[[Name|Link.jpg|alt=\"AltName\"|title='Title']]", `
`},
+ {"[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]", `
`},
+ {"[[Link]] [[Other Link]]", `Link Other Link
`},
+ {"[[Link?]]", `Link?
`},
+ {"[[Link]] [[Other Link]] [[Link?]]", `Link Other Link Link?
`},
+ {"[[markdown_file.md]]", `markdown_file.md
`},
+ {"[[file.zip]]", `file.zip
`},
+ {"[[Link #.jpg]]", `
`},
+ {"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]", `
`},
+ // FIXME: it's unable to resolve: [[link?k=v]]
+ // FIXME: it is a wrong test case, it is not an image, but a link with anchor "#.jpg"
+ {"[[some/path/Link #.jpg]]", `
`},
+ {"[[foobar]]
", `[[foobar]]
`},
+ } {
+ buffer, err := markdown.RenderString(markup.NewTestRenderContext(tree), testCase.input)
+ assert.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(testCase.expected), strings.TrimSpace(string(buffer)))
+ }
}
func Test_ParseClusterFuzz(t *testing.T) {
@@ -519,45 +382,36 @@ func TestPostProcess(t *testing.T) {
setting.StaticURLPrefix = strings.TrimSuffix(markup.TestAppURL, "/") // can't run standalone
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
- test := func(input, expected string) {
+ for _, testCase := range []struct {
+ input, expected string
+ }{
+ // Issue index shouldn't be post-processing in a document.
+ {"#1", "#1"},
+
+ // But cross-referenced issue index should work.
+ {"go-gitea/gitea#12345", `go-gitea/gitea#12345`},
+
+ // Test that other post-processing still works.
+ {":gitea:", `
`},
+ {"Some text with 😄 in the middle", `Some text with 😄 in the middle`},
+ {"http://localhost:3000/person/repo/issues/4#issuecomment-1234", `person/repo#4 (comment)`},
+ {"", ""},
+
+ // special tags, GitHub's behavior, and for unclosed tags, output as text content as much as possible
+ {"", `<script>a</script>`},
+ {"", `<style>a</STYLE>`},
+
+ // other special tags, our special behavior
+ {"go-gitea/gitea#12345`)
-
- // Test that other post-processing still works.
- test(
- ":gitea:",
- `
`)
- test(
- "Some text with 😄 in the middle",
- `Some text with 😄 in the middle`)
- test("http://localhost:3000/person/repo/issues/4#issuecomment-1234",
- `person/repo#4 (comment)`)
- test(
- "",
- "")
-
- // special tags, GitHub's behavior, and for unclosed tags, output as text content as much as possible
- test("", `<script>a</script>`)
- test("", `<style>a</STYLE>`)
-
- // other special tags, our special behavior
- test("`)
- _, _ = io.WriteString(in, `any`)
- _ = in.Close()
- assert.Equal(t, `any`, out.String())
- })
-
- t.Run("HeadNotExists", func(t *testing.T) {
- out := &bytes.Buffer{}
- var r RenderInternal
- in := r.init("sec", out, ``)
- _, _ = io.WriteString(in, ``)
- _ = in.Close()
- assert.Equal(t, ``, out.String())
- })
-
- t.Run("NotHTML", func(t *testing.T) {
- out := &bytes.Buffer{}
- var r RenderInternal
- in := r.init("sec", out, ``)
- _, _ = io.WriteString(in, ``)
- _ = in.Close()
- assert.Equal(t, ``, out.String())
- })
+ for _, test := range []struct {
+ name, input, expected string
+ }{
+ {"HeadExists", `any`, `any`},
+ {"HeadNotExists", ``, ``},
+ {"NotHTML", ``, ``},
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ out := &bytes.Buffer{}
+ var r RenderInternal
+ in := r.init("sec", out, ``)
+ _, _ = io.WriteString(in, test.input)
+ _ = in.Close()
+ assert.Equal(t, test.expected, out.String())
+ })
+ }
}
diff --git a/modules/markup/jupyter/jupyter_test.go b/modules/markup/jupyter/jupyter_test.go
index 69b69a54e9a..a6d4d65412a 100644
--- a/modules/markup/jupyter/jupyter_test.go
+++ b/modules/markup/jupyter/jupyter_test.go
@@ -16,6 +16,12 @@ import (
func TestRender(t *testing.T) {
r := renderer{}
+ render := func(t *testing.T, ctx *markup.RenderContext, input string) string {
+ var output strings.Builder
+ err := r.Render(ctx, strings.NewReader(input), &output)
+ assert.NoError(t, err)
+ return output.String()
+ }
t.Run("Basic notebook", func(t *testing.T) {
input := `{
@@ -37,12 +43,7 @@ func TestRender(t *testing.T) {
"nbformat": 4
}`
- var output strings.Builder
- ctx := &markup.RenderContext{}
- err := r.Render(ctx, strings.NewReader(input), &output)
-
- assert.NoError(t, err)
- result := output.String()
+ result := render(t, &markup.RenderContext{}, input)
assert.Contains(t, result, ``)
assert.Contains(t, result, `
`)
assert.Contains(t, result, `In [1]:`)
@@ -68,12 +69,7 @@ func TestRender(t *testing.T) {
"nbformat": 4
}`
- var output strings.Builder
- ctx := markup.NewRenderContext(t.Context())
- err := r.Render(ctx, strings.NewReader(input), &output)
-
- assert.NoError(t, err)
- result := output.String()
+ result := render(t, markup.NewRenderContext(t.Context()), input)
// Assert normal markup still renders correctly
assert.Contains(t, result, `
`)
@@ -94,12 +90,7 @@ func TestRender(t *testing.T) {
}
input := fmt.Sprintf(`{"cells": [%s], "metadata": {}, "nbformat": 4}`, strings.Join(cellBlocks, ","))
- var output strings.Builder
- ctx := markup.NewRenderContext(t.Context())
- err := r.Render(ctx, strings.NewReader(input), &output)
-
- assert.NoError(t, err)
- result := output.String()
+ result := render(t, markup.NewRenderContext(t.Context()), input)
// Verify it halts rendering gracefully and shows the truncation warning
assert.Contains(t, result, "Output truncated.")
@@ -130,12 +121,7 @@ func TestRender(t *testing.T) {
"nbformat": 4
}`
- var output strings.Builder
- ctx := markup.NewRenderContext(t.Context())
- err := r.Render(ctx, strings.NewReader(input), &output)
-
- assert.NoError(t, err)
- result := output.String()
+ result := render(t, markup.NewRenderContext(t.Context()), input)
assert.Contains(t, result, `

assert.Contains(t, result, `iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==`)
})
@@ -161,12 +147,7 @@ func TestRender(t *testing.T) {
)
`)
assert.Contains(t, result, `
`)
assert.Contains(t, result, `
1 | `)
@@ -193,12 +174,7 @@ func TestRender(t *testing.T) {
"nbformat": 4
}`
- var output strings.Builder
- ctx := markup.NewRenderContext(t.Context())
- err := r.Render(ctx, strings.NewReader(input), &output)
-
- assert.NoError(t, err)
- result := output.String()
+ result := render(t, markup.NewRenderContext(t.Context()), input)
assert.Contains(t, result, `ValueError: test error`)
assert.Contains(t, result, `cell-output-error`)
})
@@ -210,12 +186,8 @@ func TestRender(t *testing.T) {
"nbformat": 3
}`
- var output strings.Builder
- ctx := markup.NewRenderContext(t.Context())
- err := r.Render(ctx, strings.NewReader(input), &output)
-
- assert.NoError(t, err)
- assert.Regexp(t, `
This notebook uses an older format.*
`, output.String())
+ result := render(t, markup.NewRenderContext(t.Context()), input)
+ assert.Regexp(t, `
This notebook uses an older format.*
`, result)
})
}
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 2af0d41b242..259613b12c5 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -58,17 +58,6 @@ func TestRender_Images(t *testing.T) {
result := baseLink + "/.images/src/02/train.jpg" // resolved link should not go out of the base link
// hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
- render(
- "",
- `

`)
-
- render(
- "[["+title+"|"+url+"]]",
- `

`)
- render(
- "[]("+href+")",
- `

`)
-
render(
"",
`

`)
diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go
index 283d289d483..f6cd9da161b 100644
--- a/modules/markup/markdown/meta_test.go
+++ b/modules/markup/markdown/meta_test.go
@@ -26,67 +26,46 @@ func (it *IssueTemplate) Valid() bool {
}
func TestExtractMetadata(t *testing.T) {
- t.Run("ValidFrontAndBody", func(t *testing.T) {
- var meta IssueTemplate
- body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta)
- assert.NoError(t, err)
- assert.Equal(t, bodyTest, body)
- assert.Equal(t, metaTest, meta)
- assert.True(t, meta.Valid())
- })
+ for _, mode := range []string{"String", "Bytes"} {
+ t.Run(mode, func(t *testing.T) {
+ extract := func(format string, args ...any) (string, IssueTemplate, error) {
+ var meta IssueTemplate
+ if mode == "String" {
+ body, err := ExtractMetadata(fmt.Sprintf(format, args...), &meta)
+ return body, meta, err
+ } else {
+ body, err := ExtractMetadataBytes(fmt.Appendf(nil, format, args...), &meta)
+ return string(body), meta, err
+ }
+ }
- t.Run("NoFirstSeparator", func(t *testing.T) {
- var meta IssueTemplate
- _, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta)
- assert.Error(t, err)
- })
+ t.Run("ValidFrontAndBody", func(t *testing.T) {
+ body, meta, err := extract("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)
+ assert.NoError(t, err)
+ assert.Equal(t, bodyTest, body)
+ assert.Equal(t, metaTest, meta)
+ assert.True(t, meta.Valid())
+ })
- t.Run("NoLastSeparator", func(t *testing.T) {
- var meta IssueTemplate
- _, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta)
- assert.Error(t, err)
- })
+ t.Run("NoFirstSeparator", func(t *testing.T) {
+ _, _, err := extract("%s\n%s\n%s", frontTest, sepTest, bodyTest)
+ assert.Error(t, err)
+ })
- t.Run("NoBody", func(t *testing.T) {
- var meta IssueTemplate
- body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
- assert.NoError(t, err)
- assert.Empty(t, body)
- assert.Equal(t, metaTest, meta)
- assert.True(t, meta.Valid())
- })
-}
+ t.Run("NoLastSeparator", func(t *testing.T) {
+ _, _, err := extract("%s\n%s\n%s", sepTest, frontTest, bodyTest)
+ assert.Error(t, err)
+ })
-func TestExtractMetadataBytes(t *testing.T) {
- t.Run("ValidFrontAndBody", func(t *testing.T) {
- var meta IssueTemplate
- body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta)
- assert.NoError(t, err)
- assert.Equal(t, bodyTest, string(body))
- assert.Equal(t, metaTest, meta)
- assert.True(t, meta.Valid())
- })
-
- t.Run("NoFirstSeparator", func(t *testing.T) {
- var meta IssueTemplate
- _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta)
- assert.Error(t, err)
- })
-
- t.Run("NoLastSeparator", func(t *testing.T) {
- var meta IssueTemplate
- _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta)
- assert.Error(t, err)
- })
-
- t.Run("NoBody", func(t *testing.T) {
- var meta IssueTemplate
- body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
- assert.NoError(t, err)
- assert.Empty(t, string(body))
- assert.Equal(t, metaTest, meta)
- assert.True(t, meta.Valid())
- })
+ t.Run("NoBody", func(t *testing.T) {
+ body, meta, err := extract("%s\n%s\n%s", sepTest, frontTest, sepTest)
+ assert.NoError(t, err)
+ assert.Empty(t, body)
+ assert.Equal(t, metaTest, meta)
+ assert.True(t, meta.Valid())
+ })
+ })
+ }
}
var (
diff --git a/modules/markup/render_test.go b/modules/markup/render_test.go
index abbfff85d12..5423036d174 100644
--- a/modules/markup/render_test.go
+++ b/modules/markup/render_test.go
@@ -12,17 +12,12 @@ import (
)
func TestRenderIFrame(t *testing.T) {
- render := func(ctx *RenderContext, opts ExternalRendererOptions) string {
- sb := &strings.Builder{}
- require.NoError(t, RenderIFrame(ctx, &opts, sb))
- return sb.String()
- }
-
ctx := NewRenderContext(t.Context()).
WithRelativePath("tree-path").
WithMetas(map[string]string{"user": "test-owner", "repo": "test-repo", "RefTypeNameSubURL": "src/branch/master"})
// iframe doesn't need sandbox, the sandbox is set in render's response header
- ret := render(ctx, ExternalRendererOptions{ContentSandbox: "any"})
- assert.Equal(t, `
`, ret)
+ sb := &strings.Builder{}
+ require.NoError(t, RenderIFrame(ctx, &ExternalRendererOptions{ContentSandbox: "any"}, sb))
+ assert.Equal(t, `
`, sb.String())
}
diff --git a/modules/nosql/manager_redis_test.go b/modules/nosql/manager_redis_test.go
index d979ea07f6f..6a58518bd98 100644
--- a/modules/nosql/manager_redis_test.go
+++ b/modules/nosql/manager_redis_test.go
@@ -8,74 +8,26 @@ import (
"testing"
)
-func TestRedisUsernameOpt(t *testing.T) {
- uri, _ := url.Parse("redis://redis:password@myredis/0")
- opts := getRedisOptions(uri)
-
- if opts.Username != "redis" {
- t.Fail()
- }
-}
-
-func TestRedisPasswordOpt(t *testing.T) {
- uri, _ := url.Parse("redis://redis:password@myredis/0")
- opts := getRedisOptions(uri)
-
- if opts.Password != "password" {
- t.Fail()
- }
-}
-
-func TestSkipVerifyOpt(t *testing.T) {
- uri, _ := url.Parse("rediss://myredis/0?skipverify=true")
- tlsConfig := getRedisTLSOptions(uri)
-
- if !tlsConfig.InsecureSkipVerify {
- t.Fail()
- }
-}
-
-func TestInsecureSkipVerifyOpt(t *testing.T) {
- uri, _ := url.Parse("rediss://myredis/0?insecureskipverify=true")
- tlsConfig := getRedisTLSOptions(uri)
-
- if !tlsConfig.InsecureSkipVerify {
- t.Fail()
- }
-}
-
-func TestRedisSentinelUsernameOpt(t *testing.T) {
- uri, _ := url.Parse("redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass")
- opts := getRedisOptions(uri).Failover()
-
- if opts.SentinelUsername != "suser" {
- t.Fail()
- }
-}
-
-func TestRedisSentinelPasswordOpt(t *testing.T) {
- uri, _ := url.Parse("redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass")
- opts := getRedisOptions(uri).Failover()
-
- if opts.SentinelPassword != "spass" {
- t.Fail()
- }
-}
-
-func TestRedisDatabaseIndexTcp(t *testing.T) {
- uri, _ := url.Parse("redis://redis:password@myredis/12")
- opts := getRedisOptions(uri)
-
- if opts.DB != 12 {
- t.Fail()
- }
-}
-
-func TestRedisDatabaseIndexUnix(t *testing.T) {
- uri, _ := url.Parse("redis+socket:///var/run/redis.sock?database=12")
- opts := getRedisOptions(uri)
-
- if opts.DB != 12 {
- t.Fail()
+func TestRedisOptions(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ connection string
+ valid func(*url.URL) bool
+ }{
+ {"username", "redis://redis:password@myredis/0", func(uri *url.URL) bool { return getRedisOptions(uri).Username == "redis" }},
+ {"password", "redis://redis:password@myredis/0", func(uri *url.URL) bool { return getRedisOptions(uri).Password == "password" }},
+ {"skipverify", "rediss://myredis/0?skipverify=true", func(uri *url.URL) bool { return getRedisTLSOptions(uri).InsecureSkipVerify }},
+ {"insecureskipverify", "rediss://myredis/0?insecureskipverify=true", func(uri *url.URL) bool { return getRedisTLSOptions(uri).InsecureSkipVerify }},
+ {"sentinel username", "redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass", func(uri *url.URL) bool { return getRedisOptions(uri).Failover().SentinelUsername == "suser" }},
+ {"sentinel password", "redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass", func(uri *url.URL) bool { return getRedisOptions(uri).Failover().SentinelPassword == "spass" }},
+ {"tcp database", "redis://redis:password@myredis/12", func(uri *url.URL) bool { return getRedisOptions(uri).DB == 12 }},
+ {"unix database", "redis+socket:///var/run/redis.sock?database=12", func(uri *url.URL) bool { return getRedisOptions(uri).DB == 12 }},
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ uri, _ := url.Parse(test.connection)
+ if !test.valid(uri) {
+ t.Fail()
+ }
+ })
}
}
diff --git a/modules/optional/serialization_test.go b/modules/optional/serialization_test.go
index e65c53a5dbe..44bd2aac8eb 100644
--- a/modules/optional/serialization_test.go
+++ b/modules/optional/serialization_test.go
@@ -37,16 +37,22 @@ type testSerializationNullPointer struct {
OptNull optional.Option[*time.Time] `json:"opt_null,omitempty" yaml:"opt_null,omitempty"`
}
-func TestOptionalToJson(t *testing.T) {
+func TestOptionalToJsonAndYaml(t *testing.T) {
tests := []struct {
- name string
- obj *testSerializationStruct
- want string
+ name string
+ obj *testSerializationStruct
+ want string
+ wantYaml string
}{
{
name: "empty",
obj: new(testSerializationStruct),
want: `{"normal_string":"","normal_bool":false,"optional_two_bool":null,"optional_two_string":null}`,
+ wantYaml: `normal_string: ""
+normal_bool: false
+optional_two_bool: null
+optional_two_string: null
+`,
},
{
name: "some",
@@ -59,6 +65,13 @@ func TestOptionalToJson(t *testing.T) {
OptTwoString: optional.None[string](),
},
want: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_two_string":null}`,
+ wantYaml: `normal_string: a string
+normal_bool: true
+optional_bool: false
+optional_string: ""
+optional_two_bool: null
+optional_two_string: null
+`,
},
}
for _, tc := range tests {
@@ -70,6 +83,10 @@ func TestOptionalToJson(t *testing.T) {
b, err = std_json.Marshal(tc.obj)
assert.NoError(t, err)
assert.Equal(t, tc.want, string(b), "std json module returned unexpected")
+
+ b, err = yaml.Marshal(tc.obj)
+ assert.NoError(t, err)
+ assert.Equal(t, tc.wantYaml, string(b), "yaml module returned unexpected")
})
}
@@ -100,11 +117,12 @@ func TestOptionalToJson(t *testing.T) {
})
}
-func TestOptionalFromJson(t *testing.T) {
+func TestOptionalFromJsonAndYaml(t *testing.T) {
tests := []struct {
- name string
- data string
- want testSerializationStruct
+ name string
+ data string
+ dataYaml string
+ want testSerializationStruct
}{
{
name: "empty",
@@ -116,94 +134,7 @@ func TestOptionalFromJson(t *testing.T) {
{
name: "some",
data: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_two_string":null}`,
- want: testSerializationStruct{
- NormalString: "a string",
- NormalBool: true,
- OptBool: optional.Some(false),
- OptString: optional.Some(""),
- },
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- var obj1 testSerializationStruct
- err := json.Unmarshal([]byte(tc.data), &obj1)
- assert.NoError(t, err)
- assert.Equal(t, tc.want, obj1, "gitea json module returned unexpected")
-
- var obj2 testSerializationStruct
- err = std_json.Unmarshal([]byte(tc.data), &obj2)
- assert.NoError(t, err)
- assert.Equal(t, tc.want, obj2, "std json module returned unexpected")
- })
- }
-}
-
-func TestOptionalToYaml(t *testing.T) {
- tests := []struct {
- name string
- obj *testSerializationStruct
- want string
- }{
- {
- name: "empty",
- obj: new(testSerializationStruct),
- want: `normal_string: ""
-normal_bool: false
-optional_two_bool: null
-optional_two_string: null
-`,
- },
- {
- name: "some",
- obj: &testSerializationStruct{
- NormalString: "a string",
- NormalBool: true,
- OptBool: optional.Some(false),
- OptString: optional.Some(""),
- },
- want: `normal_string: a string
-normal_bool: true
-optional_bool: false
-optional_string: ""
-optional_two_bool: null
-optional_two_string: null
-`,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- b, err := yaml.Marshal(tc.obj)
- assert.NoError(t, err)
- assert.Equal(t, tc.want, string(b), "yaml module returned unexpected")
- })
- }
-}
-
-func TestOptionalFromYaml(t *testing.T) {
- tests := []struct {
- name string
- data string
- want testSerializationStruct
- }{
- {
- name: "empty",
- data: ``,
- want: testSerializationStruct{},
- },
- {
- name: "empty but init",
- data: `normal_string: ""
-normal_bool: false
-optional_bool:
-optional_two_bool:
-optional_two_string:
-`,
- want: testSerializationStruct{},
- },
- {
- name: "some",
- data: `
+ dataYaml: `
normal_string: a string
normal_bool: true
optional_bool: false
@@ -221,10 +152,31 @@ optional_two_string: null
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- var obj testSerializationStruct
- err := yaml.Unmarshal([]byte(tc.data), &obj)
- assert.NoError(t, err)
- assert.Equal(t, tc.want, obj, "yaml module returned unexpected")
+ for _, format := range []struct {
+ data string
+ unmarshal func([]byte, any) error
+ message string
+ }{
+ {tc.data, json.Unmarshal, "gitea json module returned unexpected"},
+ {tc.data, std_json.Unmarshal, "std json module returned unexpected"},
+ {tc.dataYaml, yaml.Unmarshal, "yaml module returned unexpected"},
+ } {
+ var obj testSerializationStruct
+ err := format.unmarshal([]byte(format.data), &obj)
+ assert.NoError(t, err)
+ assert.Equal(t, tc.want, obj, format.message)
+ }
})
}
+ t.Run("empty but init", func(t *testing.T) {
+ var obj testSerializationStruct
+ err := yaml.Unmarshal([]byte(`normal_string: ""
+normal_bool: false
+optional_bool:
+optional_two_bool:
+optional_two_string:
+`), &obj)
+ assert.NoError(t, err)
+ assert.Equal(t, testSerializationStruct{}, obj, "yaml module returned unexpected")
+ })
}
diff --git a/modules/packages/chef/metadata_test.go b/modules/packages/chef/metadata_test.go
index 6def4162a9d..ec7f2984125 100644
--- a/modules/packages/chef/metadata_test.go
+++ b/modules/packages/chef/metadata_test.go
@@ -22,43 +22,37 @@ const (
)
func TestParsePackage(t *testing.T) {
- t.Run("MissingMetadataFile", func(t *testing.T) {
- var buf bytes.Buffer
- zw := gzip.NewWriter(&buf)
- tw := tar.NewWriter(zw)
- tw.Close()
- zw.Close()
+ for _, name := range []string{"MissingMetadataFile", "Valid"} {
+ t.Run(name, func(t *testing.T) {
+ var buf bytes.Buffer
+ zw := gzip.NewWriter(&buf)
+ tw := tar.NewWriter(zw)
+ if name == "Valid" {
+ content := `{"name":"` + packageName + `","version":"` + packageVersion + `"}`
+ hdr := &tar.Header{
+ Name: packageName + "/metadata.json",
+ Mode: 0o600,
+ Size: int64(len(content)),
+ }
+ tw.WriteHeader(hdr)
+ tw.Write([]byte(content))
+ }
+ tw.Close()
+ zw.Close()
- p, err := ParsePackage(&buf)
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrMissingMetadataFile)
- })
-
- t.Run("Valid", func(t *testing.T) {
- var buf bytes.Buffer
- zw := gzip.NewWriter(&buf)
- tw := tar.NewWriter(zw)
-
- content := `{"name":"` + packageName + `","version":"` + packageVersion + `"}`
-
- hdr := &tar.Header{
- Name: packageName + "/metadata.json",
- Mode: 0o600,
- Size: int64(len(content)),
- }
- tw.WriteHeader(hdr)
- tw.Write([]byte(content))
-
- tw.Close()
- zw.Close()
-
- p, err := ParsePackage(&buf)
- assert.NoError(t, err)
- assert.NotNil(t, p)
- assert.Equal(t, packageName, p.Name)
- assert.Equal(t, packageVersion, p.Version)
- assert.NotNil(t, p.Metadata)
- })
+ p, err := ParsePackage(&buf)
+ if name == "MissingMetadataFile" {
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, ErrMissingMetadataFile)
+ } else {
+ assert.NoError(t, err)
+ assert.NotNil(t, p)
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageVersion, p.Version)
+ assert.NotNil(t, p.Metadata)
+ }
+ })
+ }
}
func TestParseChefMetadata(t *testing.T) {
diff --git a/modules/packages/composer/metadata_test.go b/modules/packages/composer/metadata_test.go
index ca9abac3859..c6cb196bf31 100644
--- a/modules/packages/composer/metadata_test.go
+++ b/modules/packages/composer/metadata_test.go
@@ -75,45 +75,28 @@ func TestCommentsUnmarshal(t *testing.T) {
}
func TestParsePackage(t *testing.T) {
- t.Run("MissingComposerFile", func(t *testing.T) {
- data := test.WriteZipArchive(map[string]string{"dummy.txt": ""}).Bytes()
+ for _, tc := range []struct {
+ name, filename, content string
+ expectedError error
+ }{
+ {"MissingComposerFile", "dummy.txt", "", ErrMissingComposerFile},
+ {"MissingComposerFileInRoot", "sub/sub/composer.json", "", ErrMissingComposerFile},
+ {"InvalidComposerFile", "composer.json", "", nil},
+ {"InvalidPackageName", "composer.json", "{}", ErrInvalidName},
+ {"InvalidPackageVersion", "composer.json", `{"name": "gitea/composer-package", "version": "1.a.3"}`, ErrInvalidVersion},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ data := test.WriteZipArchive(map[string]string{tc.filename: tc.content}).Bytes()
- cp, err := ParsePackage(bytes.NewReader(data))
- assert.Nil(t, cp)
- assert.ErrorIs(t, err, ErrMissingComposerFile)
- })
-
- t.Run("MissingComposerFileInRoot", func(t *testing.T) {
- data := test.WriteZipArchive(map[string]string{"sub/sub/composer.json": ""}).Bytes()
-
- cp, err := ParsePackage(bytes.NewReader(data))
- assert.Nil(t, cp)
- assert.ErrorIs(t, err, ErrMissingComposerFile)
- })
-
- t.Run("InvalidComposerFile", func(t *testing.T) {
- data := test.WriteZipArchive(map[string]string{"composer.json": ""}).Bytes()
-
- cp, err := ParsePackage(bytes.NewReader(data))
- assert.Nil(t, cp)
- assert.Error(t, err)
- })
-
- t.Run("InvalidPackageName", func(t *testing.T) {
- data := test.WriteZipArchive(map[string]string{"composer.json": "{}"}).Bytes()
-
- cp, err := ParsePackage(bytes.NewReader(data))
- assert.Nil(t, cp)
- assert.ErrorIs(t, err, ErrInvalidName)
- })
-
- t.Run("InvalidPackageVersion", func(t *testing.T) {
- data := test.WriteZipArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "version": "1.a.3"}`}).Bytes()
-
- cp, err := ParsePackage(bytes.NewReader(data))
- assert.Nil(t, cp)
- assert.ErrorIs(t, err, ErrInvalidVersion)
- })
+ cp, err := ParsePackage(bytes.NewReader(data))
+ assert.Nil(t, cp)
+ if tc.expectedError == nil {
+ assert.Error(t, err)
+ } else {
+ assert.ErrorIs(t, err, tc.expectedError)
+ }
+ })
+ }
t.Run("InvalidReadmePath", func(t *testing.T) {
data := test.WriteZipArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "readme": "sub/README.md"}`}).Bytes()
diff --git a/modules/packages/conan/reference_test.go b/modules/packages/conan/reference_test.go
index 6ea86eb0ddc..8b45e61ebe5 100644
--- a/modules/packages/conan/reference_test.go
+++ b/modules/packages/conan/reference_test.go
@@ -49,45 +49,25 @@ func TestNewRecipeReference(t *testing.T) {
}
func TestRecipeReferenceRevisionOrDefault(t *testing.T) {
- rref, err := NewRecipeReference("name", "1.0", "", "", "")
- assert.NoError(t, err)
- assert.Equal(t, DefaultRevision, rref.RevisionOrDefault())
-
- rref, err = NewRecipeReference("name", "1.0", "", "", DefaultRevision)
- assert.NoError(t, err)
- assert.Equal(t, DefaultRevision, rref.RevisionOrDefault())
-
- rref, err = NewRecipeReference("name", "1.0", "", "", "Az09")
- assert.NoError(t, err)
- assert.Equal(t, "Az09", rref.RevisionOrDefault())
-}
-
-func TestRecipeReferenceString(t *testing.T) {
- rref, err := NewRecipeReference("name", "1.0", "", "", "")
- assert.NoError(t, err)
- assert.Equal(t, "name/1.0", rref.String())
-
- rref, err = NewRecipeReference("name", "1.0", "user", "channel", "")
- assert.NoError(t, err)
- assert.Equal(t, "name/1.0@user/channel", rref.String())
-
- rref, err = NewRecipeReference("name", "1.0", "user", "channel", "Az09")
- assert.NoError(t, err)
- assert.Equal(t, "name/1.0@user/channel#Az09", rref.String())
-}
-
-func TestRecipeReferenceLinkName(t *testing.T) {
- rref, err := NewRecipeReference("name", "1.0", "", "", "")
- assert.NoError(t, err)
- assert.Equal(t, "name/1.0/_/_/0", rref.LinkName())
-
- rref, err = NewRecipeReference("name", "1.0", "user", "channel", "")
- assert.NoError(t, err)
- assert.Equal(t, "name/1.0/user/channel/0", rref.LinkName())
-
- rref, err = NewRecipeReference("name", "1.0", "user", "channel", "Az09")
- assert.NoError(t, err)
- assert.Equal(t, "name/1.0/user/channel/Az09", rref.LinkName())
+ for _, test := range []struct {
+ user, channel, revision string
+ method func(*RecipeReference) string
+ expected string
+ }{
+ {"", "", "", (*RecipeReference).RevisionOrDefault, DefaultRevision},
+ {"", "", DefaultRevision, (*RecipeReference).RevisionOrDefault, DefaultRevision},
+ {"", "", "Az09", (*RecipeReference).RevisionOrDefault, "Az09"},
+ {"", "", "", (*RecipeReference).String, "name/1.0"},
+ {"user", "channel", "", (*RecipeReference).String, "name/1.0@user/channel"},
+ {"user", "channel", "Az09", (*RecipeReference).String, "name/1.0@user/channel#Az09"},
+ {"", "", "", (*RecipeReference).LinkName, "name/1.0/_/_/0"},
+ {"user", "channel", "", (*RecipeReference).LinkName, "name/1.0/user/channel/0"},
+ {"user", "channel", "Az09", (*RecipeReference).LinkName, "name/1.0/user/channel/Az09"},
+ } {
+ rref, err := NewRecipeReference("name", "1.0", test.user, test.channel, test.revision)
+ assert.NoError(t, err)
+ assert.Equal(t, test.expected, test.method(rref))
+ }
}
func TestNewPackageReference(t *testing.T) {
@@ -121,27 +101,19 @@ func TestNewPackageReference(t *testing.T) {
func TestPackageReferenceRevisionOrDefault(t *testing.T) {
rref, _ := NewRecipeReference("name", "1.0", "", "", "")
- pref, err := NewPackageReference(rref, "ref", "")
- assert.NoError(t, err)
- assert.Equal(t, DefaultRevision, pref.RevisionOrDefault())
-
- pref, err = NewPackageReference(rref, "ref", DefaultRevision)
- assert.NoError(t, err)
- assert.Equal(t, DefaultRevision, pref.RevisionOrDefault())
-
- pref, err = NewPackageReference(rref, "ref", "Az09")
- assert.NoError(t, err)
- assert.Equal(t, "Az09", pref.RevisionOrDefault())
-}
-
-func TestPackageReferenceLinkName(t *testing.T) {
- rref, _ := NewRecipeReference("name", "1.0", "", "", "")
-
- pref, err := NewPackageReference(rref, "ref", "")
- assert.NoError(t, err)
- assert.Equal(t, "ref/0", pref.LinkName())
-
- pref, err = NewPackageReference(rref, "ref", "Az09")
- assert.NoError(t, err)
- assert.Equal(t, "ref/Az09", pref.LinkName())
+ for _, test := range []struct {
+ revision string
+ method func(*PackageReference) string
+ expected string
+ }{
+ {"", (*PackageReference).RevisionOrDefault, DefaultRevision},
+ {DefaultRevision, (*PackageReference).RevisionOrDefault, DefaultRevision},
+ {"Az09", (*PackageReference).RevisionOrDefault, "Az09"},
+ {"", (*PackageReference).LinkName, "ref/0"},
+ {"Az09", (*PackageReference).LinkName, "ref/Az09"},
+ } {
+ pref, err := NewPackageReference(rref, "ref", test.revision)
+ assert.NoError(t, err)
+ assert.Equal(t, test.expected, test.method(pref))
+ }
}
diff --git a/modules/packages/cran/metadata_test.go b/modules/packages/cran/metadata_test.go
index c7b09ba4670..413044594b2 100644
--- a/modules/packages/cran/metadata_test.go
+++ b/modules/packages/cran/metadata_test.go
@@ -37,8 +37,11 @@ func createDescription(name, version string) *bytes.Buffer {
}
func TestParsePackage(t *testing.T) {
- t.Run(".tar.gz", func(t *testing.T) {
- createArchive := func(filename string, content []byte) *bytes.Reader {
+ for _, archive := range []struct {
+ extension string
+ create func(string, []byte) *bytes.Reader
+ }{
+ {".tar.gz", func(filename string, content []byte) *bytes.Reader {
var buf bytes.Buffer
gw := gzip.NewWriter(&buf)
tw := tar.NewWriter(gw)
@@ -52,70 +55,43 @@ func TestParsePackage(t *testing.T) {
tw.Close()
gw.Close()
return bytes.NewReader(buf.Bytes())
- }
-
- t.Run("MissingDescriptionFile", func(t *testing.T) {
- buf := createArchive(
- "dummy.txt",
- []byte{},
- )
-
- p, err := ParsePackage(buf, buf.Size())
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrMissingDescriptionFile)
- })
-
- t.Run("Valid", func(t *testing.T) {
- buf := createArchive(
- "package/DESCRIPTION",
- createDescription(packageName, packageVersion).Bytes(),
- )
-
- p, err := ParsePackage(buf, buf.Size())
-
- assert.NotNil(t, p)
- assert.NoError(t, err)
-
- assert.Equal(t, packageName, p.Name)
- assert.Equal(t, packageVersion, p.Version)
- })
- })
-
- t.Run(".zip", func(t *testing.T) {
- createArchive := func(filename string, content []byte) *bytes.Reader {
+ }},
+ {".zip", func(filename string, content []byte) *bytes.Reader {
var buf bytes.Buffer
archive := zip.NewWriter(&buf)
w, _ := archive.Create(filename)
w.Write(content)
archive.Close()
return bytes.NewReader(buf.Bytes())
- }
+ }},
+ } {
+ t.Run(archive.extension, func(t *testing.T) {
+ t.Run("MissingDescriptionFile", func(t *testing.T) {
+ buf := archive.create(
+ "dummy.txt",
+ []byte{},
+ )
- t.Run("MissingDescriptionFile", func(t *testing.T) {
- buf := createArchive(
- "dummy.txt",
- []byte{},
- )
+ p, err := ParsePackage(buf, buf.Size())
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, ErrMissingDescriptionFile)
+ })
- p, err := ParsePackage(buf, buf.Size())
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrMissingDescriptionFile)
+ t.Run("Valid", func(t *testing.T) {
+ buf := archive.create(
+ "package/DESCRIPTION",
+ createDescription(packageName, packageVersion).Bytes(),
+ )
+
+ p, err := ParsePackage(buf, buf.Size())
+ assert.NotNil(t, p)
+ assert.NoError(t, err)
+
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageVersion, p.Version)
+ })
})
-
- t.Run("Valid", func(t *testing.T) {
- buf := createArchive(
- "package/DESCRIPTION",
- createDescription(packageName, packageVersion).Bytes(),
- )
-
- p, err := ParsePackage(buf, buf.Size())
- assert.NotNil(t, p)
- assert.NoError(t, err)
-
- assert.Equal(t, packageName, p.Name)
- assert.Equal(t, packageVersion, p.Version)
- })
- })
+ }
}
func TestParseDescription(t *testing.T) {
diff --git a/modules/packages/debian/metadata_test.go b/modules/packages/debian/metadata_test.go
index 20d8c5f509c..603eac45d1f 100644
--- a/modules/packages/debian/metadata_test.go
+++ b/modules/packages/debian/metadata_test.go
@@ -110,21 +110,14 @@ func TestParsePackage(t *testing.T) {
w.Write(buf.Bytes())
w.Close()
- data := createArchive(map[string][]byte{"control.tar" + c.Extension: cbuf.Bytes()})
-
- p, err := ParsePackage(data)
- assert.NotNil(t, p)
- assert.NoError(t, err)
- assert.Equal(t, "gitea", p.Name)
-
- t.Run("TrailingSlash", func(t *testing.T) {
- data := createArchive(map[string][]byte{"control.tar" + c.Extension + "/": cbuf.Bytes()})
+ for _, suffix := range []string{"", "/"} {
+ data := createArchive(map[string][]byte{"control.tar" + c.Extension + suffix: cbuf.Bytes()})
p, err := ParsePackage(data)
assert.NotNil(t, p)
assert.NoError(t, err)
assert.Equal(t, "gitea", p.Name)
- })
+ }
})
}
})
diff --git a/modules/packages/goproxy/metadata_test.go b/modules/packages/goproxy/metadata_test.go
index 24db3596510..edf52d93dd4 100644
--- a/modules/packages/goproxy/metadata_test.go
+++ b/modules/packages/goproxy/metadata_test.go
@@ -28,58 +28,41 @@ func TestParsePackage(t *testing.T) {
return bytes.NewReader(buf.Bytes())
}
- t.Run("EmptyPackage", func(t *testing.T) {
- data := createArchive(nil)
-
- p, err := ParsePackage(data, int64(data.Len()))
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidStructure)
- })
-
- t.Run("InvalidNameOrVersionStructure", func(t *testing.T) {
- data := createArchive(map[string][]byte{
+ for _, testCase := range []struct {
+ name string
+ files map[string][]byte
+ expectedError error
+ goMod string
+ }{
+ {"EmptyPackage", nil, ErrInvalidStructure, ""},
+ {"InvalidNameOrVersionStructure", map[string][]byte{
packageName + "/" + packageVersion + "/go.mod": {},
- })
-
- p, err := ParsePackage(data, int64(data.Len()))
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidStructure)
- })
-
- t.Run("GoModFileInWrongDirectory", func(t *testing.T) {
- data := createArchive(map[string][]byte{
+ }, ErrInvalidStructure, ""},
+ {"GoModFileInWrongDirectory", map[string][]byte{
packageName + "@" + packageVersion + "/subdir/go.mod": {},
- })
-
- p, err := ParsePackage(data, int64(data.Len()))
- assert.NotNil(t, p)
- assert.NoError(t, err)
- assert.Equal(t, packageName, p.Name)
- assert.Equal(t, packageVersion, p.Version)
- assert.Equal(t, "module gitea.com/go-gitea/gitea", p.GoMod)
- })
-
- t.Run("InvalidVersion", func(t *testing.T) {
- data := createArchive(map[string][]byte{
+ }, nil, "module gitea.com/go-gitea/gitea"},
+ {"InvalidVersion", map[string][]byte{
packageName + "@v1.0.0\nv99.0.0/go.mod": []byte("module " + packageName),
- })
-
- p, err := ParsePackage(data, int64(data.Len()))
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidVersion)
- })
-
- t.Run("Valid", func(t *testing.T) {
- data := createArchive(map[string][]byte{
+ }, ErrInvalidVersion, ""},
+ {"Valid", map[string][]byte{
packageName + "@" + packageVersion + "/subdir/go.mod": []byte("invalid"),
packageName + "@" + packageVersion + "/go.mod": []byte("valid"),
- })
+ }, nil, "valid"},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ data := createArchive(testCase.files)
- p, err := ParsePackage(data, int64(data.Len()))
- assert.NotNil(t, p)
- assert.NoError(t, err)
- assert.Equal(t, packageName, p.Name)
- assert.Equal(t, packageVersion, p.Version)
- assert.Equal(t, "valid", p.GoMod)
- })
+ p, err := ParsePackage(data, int64(data.Len()))
+ if testCase.expectedError != nil {
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, testCase.expectedError)
+ } else {
+ assert.NotNil(t, p)
+ assert.NoError(t, err)
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageVersion, p.Version)
+ assert.Equal(t, testCase.goMod, p.GoMod)
+ }
+ })
+ }
}
diff --git a/modules/packages/multi_hasher_test.go b/modules/packages/multi_hasher_test.go
index a37debbc95f..147de22192b 100644
--- a/modules/packages/multi_hasher_test.go
+++ b/modules/packages/multi_hasher_test.go
@@ -18,36 +18,28 @@ const (
)
func TestMultiHasherSums(t *testing.T) {
- t.Run("Sums", func(t *testing.T) {
- h := NewMultiHasher()
- h.Write([]byte("gitea"))
+ for _, content := range []string{"gitea", "git"} {
+ t.Run(content, func(t *testing.T) {
+ h := NewMultiHasher()
+ h.Write([]byte(content))
- hashMD5, hashSHA1, hashSHA256, hashSHA512 := h.Sums()
+ if content == "git" {
+ state, err := h.MarshalBinary()
+ assert.NoError(t, err)
- assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5))
- assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1))
- assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256))
- assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512))
- })
+ h = NewMultiHasher()
+ err = h.UnmarshalBinary(state)
+ assert.NoError(t, err)
- t.Run("State", func(t *testing.T) {
- h := NewMultiHasher()
- h.Write([]byte("git"))
+ h.Write([]byte("ea"))
+ }
- state, err := h.MarshalBinary()
- assert.NoError(t, err)
+ hashMD5, hashSHA1, hashSHA256, hashSHA512 := h.Sums()
- h2 := NewMultiHasher()
- err = h2.UnmarshalBinary(state)
- assert.NoError(t, err)
-
- h2.Write([]byte("ea"))
-
- hashMD5, hashSHA1, hashSHA256, hashSHA512 := h2.Sums()
-
- assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5))
- assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1))
- assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256))
- assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512))
- })
+ assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5))
+ assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1))
+ assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256))
+ assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512))
+ })
+ }
}
diff --git a/modules/packages/npm/creator_test.go b/modules/packages/npm/creator_test.go
index 1728f5d0224..7770601f5d0 100644
--- a/modules/packages/npm/creator_test.go
+++ b/modules/packages/npm/creator_test.go
@@ -52,71 +52,38 @@ func TestParsePackage(t *testing.T) {
assert.ErrorIs(t, err, ErrInvalidPackage)
})
- t.Run("InvalidPackageName", func(t *testing.T) {
- test := func(t *testing.T, name string) {
- p, err := parseUploadPackage(&packageUpload{
- PackageMetadata: PackageMetadata{
- ID: name,
- Name: name,
- Versions: map[string]*PackageMetadataVersion{
- packageVersion: {
- Name: name,
+ for _, testCase := range []struct {
+ name string
+ names []string
+ expectedError error
+ }{
+ {"InvalidPackageName", []string{
+ " test ", " test", "test ", "te st", "Test", "_test", ".test", "^test", "te^st",
+ "te|st", "te)(st", "te'st", "te!st", "te*st", "te~st", "invalid/scope", "@invalid/_name", "@invalid/.name",
+ }, ErrInvalidPackageName},
+ {"ValidPackageName", []string{
+ "test", "@scope/name", "@scope/q", "q", "@scope/package-name", "@scope/package.name", "@scope/package_name",
+ "123name", "----", packageFullName,
+ }, ErrInvalidPackageVersion},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ for _, name := range testCase.names {
+ p, err := parseUploadPackage(&packageUpload{
+ PackageMetadata: PackageMetadata{
+ ID: name,
+ Name: name,
+ Versions: map[string]*PackageMetadataVersion{
+ packageVersion: {
+ Name: name,
+ },
},
},
- },
- })
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidPackageName)
- }
-
- test(t, " test ")
- test(t, " test")
- test(t, "test ")
- test(t, "te st")
- test(t, "Test")
- test(t, "_test")
- test(t, ".test")
- test(t, "^test")
- test(t, "te^st")
- test(t, "te|st")
- test(t, "te)(st")
- test(t, "te'st")
- test(t, "te!st")
- test(t, "te*st")
- test(t, "te~st")
- test(t, "invalid/scope")
- test(t, "@invalid/_name")
- test(t, "@invalid/.name")
- })
-
- t.Run("ValidPackageName", func(t *testing.T) {
- test := func(t *testing.T, name string) {
- p, err := parseUploadPackage(&packageUpload{
- PackageMetadata: PackageMetadata{
- ID: name,
- Name: name,
- Versions: map[string]*PackageMetadataVersion{
- packageVersion: {
- Name: name,
- },
- },
- },
- })
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidPackageVersion)
- }
-
- test(t, "test")
- test(t, "@scope/name")
- test(t, "@scope/q")
- test(t, "q")
- test(t, "@scope/package-name")
- test(t, "@scope/package.name")
- test(t, "@scope/package_name")
- test(t, "123name")
- test(t, "----")
- test(t, packageFullName)
- })
+ })
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, testCase.expectedError)
+ }
+ })
+ }
t.Run("InvalidPackageVersion", func(t *testing.T) {
version := "first-version"
@@ -136,108 +103,42 @@ func TestParsePackage(t *testing.T) {
assert.ErrorIs(t, err, ErrInvalidPackageVersion)
})
- t.Run("InvalidAttachment", func(t *testing.T) {
- b, _ := json.Marshal(packageUpload{
- PackageMetadata: PackageMetadata{
- ID: packageFullName,
- Name: packageFullName,
- Versions: map[string]*PackageMetadataVersion{
- packageVersion: {
- Name: packageFullName,
- Version: packageVersion,
- },
- },
- },
- Attachments: map[string]*PackageAttachment{
- "dummy.tgz": {},
- },
- })
-
- p, _, err := ParseUpload(bytes.NewReader(b))
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidAttachment)
- })
-
- t.Run("InvalidData", func(t *testing.T) {
- filename := fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion)
- b, _ := json.Marshal(packageUpload{
- PackageMetadata: PackageMetadata{
- ID: packageFullName,
- Name: packageFullName,
- Versions: map[string]*PackageMetadataVersion{
- packageVersion: {
- Name: packageFullName,
- Version: packageVersion,
- },
- },
- },
- Attachments: map[string]*PackageAttachment{
- filename: {
- Data: "/",
- },
- },
- })
-
- p, _, err := ParseUpload(bytes.NewReader(b))
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidAttachment)
- })
-
- t.Run("InvalidIntegrity", func(t *testing.T) {
- filename := fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion)
- b, _ := json.Marshal(packageUpload{
- PackageMetadata: PackageMetadata{
- ID: packageFullName,
- Name: packageFullName,
- Versions: map[string]*PackageMetadataVersion{
- packageVersion: {
- Name: packageFullName,
- Version: packageVersion,
- Dist: PackageDistribution{
- Integrity: "sha512-test==",
+ for _, testCase := range []struct {
+ name, filename, data, integrity string
+ expectedError error
+ }{
+ {"InvalidAttachment", "dummy.tgz", "", "", ErrInvalidAttachment},
+ {"InvalidData", fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion), "/", "", ErrInvalidAttachment},
+ {"InvalidIntegrity", fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion), data, "sha512-test==", ErrInvalidIntegrity},
+ {"InvalidIntegrity2", fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion), base64.StdEncoding.EncodeToString([]byte("data")), integrity, ErrInvalidIntegrity},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ b, _ := json.Marshal(packageUpload{
+ PackageMetadata: PackageMetadata{
+ ID: packageFullName,
+ Name: packageFullName,
+ Versions: map[string]*PackageMetadataVersion{
+ packageVersion: {
+ Name: packageFullName,
+ Version: packageVersion,
+ Dist: PackageDistribution{
+ Integrity: testCase.integrity,
+ },
},
},
},
- },
- Attachments: map[string]*PackageAttachment{
- filename: {
- Data: data,
- },
- },
- })
-
- p, _, err := ParseUpload(bytes.NewReader(b))
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidIntegrity)
- })
-
- t.Run("InvalidIntegrity2", func(t *testing.T) {
- filename := fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion)
- b, _ := json.Marshal(packageUpload{
- PackageMetadata: PackageMetadata{
- ID: packageFullName,
- Name: packageFullName,
- Versions: map[string]*PackageMetadataVersion{
- packageVersion: {
- Name: packageFullName,
- Version: packageVersion,
- Dist: PackageDistribution{
- Integrity: integrity,
- },
+ Attachments: map[string]*PackageAttachment{
+ testCase.filename: {
+ Data: testCase.data,
},
},
- },
- Attachments: map[string]*PackageAttachment{
- filename: {
- Data: base64.StdEncoding.EncodeToString([]byte("data")),
- },
- },
- })
+ })
- p, _, err := ParseUpload(bytes.NewReader(b))
- assert.Nil(t, p)
- assert.ErrorIs(t, err, ErrInvalidIntegrity)
- })
+ p, _, err := ParseUpload(bytes.NewReader(b))
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, testCase.expectedError)
+ })
+ }
t.Run("Valid", func(t *testing.T) {
filename := fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion)
diff --git a/modules/packages/nuget/metadata_test.go b/modules/packages/nuget/metadata_test.go
index 564329b4df7..6ba81ac86d6 100644
--- a/modules/packages/nuget/metadata_test.go
+++ b/modules/packages/nuget/metadata_test.go
@@ -92,53 +92,37 @@ func TestParsePackageMetaData(t *testing.T) {
return buf.Bytes()
}
- t.Run("MissingNuspecFile", func(t *testing.T) {
- data := createArchive(map[string]string{"dummy.txt": ""})
-
- np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
- assert.Nil(t, np)
- assert.ErrorIs(t, err, ErrMissingNuspecFile)
- })
-
- t.Run("MissingNuspecFileInRoot", func(t *testing.T) {
- data := createArchive(map[string]string{"sub/package.nuspec": ""})
-
- np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
- assert.Nil(t, np)
- assert.ErrorIs(t, err, ErrMissingNuspecFile)
- })
-
- t.Run("InvalidNuspecFile", func(t *testing.T) {
- data := createArchive(map[string]string{"package.nuspec": ""})
-
- np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
- assert.Nil(t, np)
- assert.Error(t, err)
- })
-
- t.Run("InvalidPackageId", func(t *testing.T) {
- data := createArchive(map[string]string{"package.nuspec": `
+ for _, testCase := range []struct {
+ name string
+ files map[string]string
+ expectedError error
+ }{
+ {"MissingNuspecFile", map[string]string{"dummy.txt": ""}, ErrMissingNuspecFile},
+ {"MissingNuspecFileInRoot", map[string]string{"sub/package.nuspec": ""}, ErrMissingNuspecFile},
+ {"InvalidNuspecFile", map[string]string{"package.nuspec": ""}, nil},
+ {"InvalidPackageId", map[string]string{"package.nuspec": `
- `})
-
- np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
- assert.Nil(t, np)
- assert.ErrorIs(t, err, ErrNuspecInvalidID)
- })
-
- t.Run("InvalidPackageVersion", func(t *testing.T) {
- data := createArchive(map[string]string{"package.nuspec": `
+ `}, ErrNuspecInvalidID},
+ {"InvalidPackageVersion", map[string]string{"package.nuspec": `
` + id + `
- `})
+ `}, ErrNuspecInvalidVersion},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ data := createArchive(testCase.files)
- np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
- assert.Nil(t, np)
- assert.ErrorIs(t, err, ErrNuspecInvalidVersion)
- })
+ np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
+ assert.Nil(t, np)
+ if testCase.expectedError == nil {
+ assert.Error(t, err)
+ } else {
+ assert.ErrorIs(t, err, testCase.expectedError)
+ }
+ })
+ }
t.Run("MissingReadme", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": nuspecContent})
diff --git a/modules/packages/rpm/metadata_test.go b/modules/packages/rpm/metadata_test.go
index 6ae66640434..840f3b5621a 100644
--- a/modules/packages/rpm/metadata_test.go
+++ b/modules/packages/rpm/metadata_test.go
@@ -110,49 +110,22 @@ OvSXaRmpzyTLHvsXg/M9EcwbAAA=`
},
p.FileMetadata.Provides,
)
- assert.ElementsMatch(
- t,
- []*Entry{
- {
- Name: "/bin/sh",
- },
- {
- Name: "/bin/sh",
- },
- {
- Name: "/bin/sh",
- },
- {
- Name: "rpmlib(CompressedFileNames)",
- Flags: "LE",
- Version: "3.0.4",
- Epoch: "0",
- Release: "1",
- },
- {
- Name: "rpmlib(FileDigests)",
- Flags: "LE",
- Version: "4.6.0",
- Epoch: "0",
- Release: "1",
- },
- {
- Name: "rpmlib(PayloadFilesHavePrefix)",
- Flags: "LE",
- Version: "4.0",
- Epoch: "0",
- Release: "1",
- },
- {
- Name: "rpmlib(PayloadIsXz)",
- Flags: "LE",
- Version: "5.2",
- Epoch: "0",
- Release: "1",
- },
- },
- p.FileMetadata.Requires,
- )
+ requires := []*Entry{{Name: "/bin/sh"}, {Name: "/bin/sh"}, {Name: "/bin/sh"}}
+ for _, dependency := range []struct{ name, version string }{
+ {"rpmlib(CompressedFileNames)", "3.0.4"},
+ {"rpmlib(FileDigests)", "4.6.0"},
+ {"rpmlib(PayloadFilesHavePrefix)", "4.0"},
+ {"rpmlib(PayloadIsXz)", "5.2"},
+ } {
+ requires = append(requires, &Entry{
+ Name: dependency.name,
+ Flags: "LE",
+ Version: dependency.version,
+ Epoch: "0",
+ Release: "1",
+ })
+ }
+ assert.ElementsMatch(t, requires, p.FileMetadata.Requires)
assert.ElementsMatch(
t,
[]*File{
diff --git a/modules/packages/swift/metadata_test.go b/modules/packages/swift/metadata_test.go
index ac77c90c1f6..71f0fa1f329 100644
--- a/modules/packages/swift/metadata_test.go
+++ b/modules/packages/swift/metadata_test.go
@@ -164,15 +164,15 @@ func TestParsePackage(t *testing.T) {
assert.Equal(t, contentA, p.Metadata.Manifests[""].Content)
})
- t.Run("WithMetadata", func(t *testing.T) {
+ parseWithMetadata := func(metadata string) (*Package, error) {
data := test.WriteZipArchive(map[string]string{
"Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift",
})
+ return ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), strings.NewReader(metadata))
+ }
- p, err := ParsePackage(
- bytes.NewReader(data.Bytes()), int64(data.Len()),
- strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","keywords":["swift","package"],"license":"`+packageLicense+`","licenseURL":"`+packageLicenseURL+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`),
- )
+ t.Run("WithMetadata", func(t *testing.T) {
+ p, err := parseWithMetadata(`{"name":"` + packageName + `","version":"` + packageVersion + `","description":"` + packageDescription + `","keywords":["swift","package"],"license":"` + packageLicense + `","licenseURL":"` + packageLicenseURL + `","codeRepository":"` + packageRepositoryURL + `","author":{"givenName":"` + packageAuthor + `"},"repositoryURLs":["` + packageRepositoryURL + `"]}`)
assert.NotNil(t, p)
assert.NoError(t, err)
@@ -192,15 +192,8 @@ func TestParsePackage(t *testing.T) {
})
t.Run("WithExplicitNameField", func(t *testing.T) {
- data := test.WriteZipArchive(map[string]string{
- "Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift",
- })
-
authorName := "John Doe"
- p, err := ParsePackage(
- bytes.NewReader(data.Bytes()), int64(data.Len()),
- strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","author":{"name":"`+authorName+`","givenName":"John","familyName":"Doe"}}`),
- )
+ p, err := parseWithMetadata(`{"name":"` + packageName + `","version":"` + packageVersion + `","description":"` + packageDescription + `","author":{"name":"` + authorName + `","givenName":"John","familyName":"Doe"}}`)
assert.NotNil(t, p)
assert.NoError(t, err)
@@ -210,14 +203,7 @@ func TestParsePackage(t *testing.T) {
})
t.Run("WithEmptyJSONMetadata", func(t *testing.T) {
- data := test.WriteZipArchive(map[string]string{
- "Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift",
- })
-
- p, err := ParsePackage(
- bytes.NewReader(data.Bytes()), int64(data.Len()),
- strings.NewReader(`{}`),
- )
+ p, err := parseWithMetadata(`{}`)
assert.NotNil(t, p)
assert.NoError(t, err)
assert.NotNil(t, p.Metadata)
@@ -226,15 +212,8 @@ func TestParsePackage(t *testing.T) {
})
t.Run("NameFieldGeneration", func(t *testing.T) {
- data := test.WriteZipArchive(map[string]string{
- "Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift",
- })
-
// Test with only individual name components - Name should be auto-generated
- p, err := ParsePackage(
- bytes.NewReader(data.Bytes()), int64(data.Len()),
- strings.NewReader(`{"author":{"givenName":"John","middleName":"Q","familyName":"Doe"}}`),
- )
+ p, err := parseWithMetadata(`{"author":{"givenName":"John","middleName":"Q","familyName":"Doe"}}`)
assert.NotNil(t, p)
assert.NoError(t, err)
assert.Equal(t, "John Q Doe", p.Metadata.Author.Name)
@@ -245,42 +224,16 @@ func TestParsePackage(t *testing.T) {
}
func TestTrimmedVersionString(t *testing.T) {
- cases := []struct {
- Version *version.Version
+ for _, c := range []struct {
+ Versions []string
Expected string
}{
- {
- Version: version.Must(version.NewVersion("1")),
- Expected: "1.0",
- },
- {
- Version: version.Must(version.NewVersion("1.0")),
- Expected: "1.0",
- },
- {
- Version: version.Must(version.NewVersion("1.0.0")),
- Expected: "1.0",
- },
- {
- Version: version.Must(version.NewVersion("1.0.1")),
- Expected: "1.0.1",
- },
- {
- Version: version.Must(version.NewVersion("1.0+meta")),
- Expected: "1.0",
- },
- {
- Version: version.Must(version.NewVersion("1.0.0+meta")),
- Expected: "1.0",
- },
- {
- Version: version.Must(version.NewVersion("1.0.1+meta")),
- Expected: "1.0.1",
- },
- }
-
- for _, c := range cases {
- assert.Equal(t, c.Expected, TrimmedVersionString(c.Version))
+ {[]string{"1", "1.0", "1.0.0", "1.0+meta", "1.0.0+meta"}, "1.0"},
+ {[]string{"1.0.1", "1.0.1+meta"}, "1.0.1"},
+ } {
+ for _, input := range c.Versions {
+ assert.Equal(t, c.Expected, TrimmedVersionString(version.Must(version.NewVersion(input))))
+ }
}
}
diff --git a/modules/packages/vagrant/metadata_test.go b/modules/packages/vagrant/metadata_test.go
index ab42c65f9f6..eac65c6d783 100644
--- a/modules/packages/vagrant/metadata_test.go
+++ b/modules/packages/vagrant/metadata_test.go
@@ -49,29 +49,6 @@ func TestParseMetadataFromBox(t *testing.T) {
assert.NoError(t, err)
})
- t.Run("Valid", func(t *testing.T) {
- content, err := json.Marshal(map[string]string{
- "description": description,
- "author": author,
- "website": projectURL,
- "repository": repositoryURL,
- })
- assert.NoError(t, err)
-
- data := createArchive(map[string][]byte{"info.json": content})
-
- metadata, err := ParseMetadataFromBox(data)
- assert.NotNil(t, metadata)
- assert.NoError(t, err)
-
- assert.Equal(t, author, metadata.Author)
- assert.Equal(t, description, metadata.Description)
- assert.Equal(t, projectURL, metadata.ProjectURL)
- assert.Equal(t, repositoryURL, metadata.RepositoryURL)
- })
-}
-
-func TestParseInfoFile(t *testing.T) {
t.Run("UnknownKeys", func(t *testing.T) {
content, err := json.Marshal(map[string]string{
"package": "",
@@ -89,22 +66,31 @@ func TestParseInfoFile(t *testing.T) {
assert.Empty(t, metadata.RepositoryURL)
})
- t.Run("Valid", func(t *testing.T) {
- content, err := json.Marshal(map[string]string{
- "description": description,
- "author": author,
- "website": projectURL,
- "repository": repositoryURL,
+ for name, parse := range map[string]func([]byte) (*Metadata, error){
+ "ValidBox": func(content []byte) (*Metadata, error) {
+ return ParseMetadataFromBox(createArchive(map[string][]byte{"info.json": content}))
+ },
+ "ValidInfoFile": func(content []byte) (*Metadata, error) {
+ return ParseInfoFile(bytes.NewReader(content))
+ },
+ } {
+ t.Run(name, func(t *testing.T) {
+ content, err := json.Marshal(map[string]string{
+ "description": description,
+ "author": author,
+ "website": projectURL,
+ "repository": repositoryURL,
+ })
+ assert.NoError(t, err)
+
+ metadata, err := parse(content)
+ assert.NotNil(t, metadata)
+ assert.NoError(t, err)
+
+ assert.Equal(t, author, metadata.Author)
+ assert.Equal(t, description, metadata.Description)
+ assert.Equal(t, projectURL, metadata.ProjectURL)
+ assert.Equal(t, repositoryURL, metadata.RepositoryURL)
})
- assert.NoError(t, err)
-
- metadata, err := ParseInfoFile(bytes.NewReader(content))
- assert.NotNil(t, metadata)
- assert.NoError(t, err)
-
- assert.Equal(t, author, metadata.Author)
- assert.Equal(t, description, metadata.Description)
- assert.Equal(t, projectURL, metadata.ProjectURL)
- assert.Equal(t, repositoryURL, metadata.RepositoryURL)
- })
+ }
}
diff --git a/modules/paginator/paginator_test.go b/modules/paginator/paginator_test.go
index ed46ecea94c..16fa8556505 100644
--- a/modules/paginator/paginator_test.go
+++ b/modules/paginator/paginator_test.go
@@ -85,226 +85,71 @@ func TestPaginator(t *testing.T) {
assert.True(t, pages[0].IsCurrent())
})
+ assertPages := func(t *testing.T, total, current, numPages int, expected ...int) {
+ pages := New(total, 10, current, numPages).Pages()
+ assert.Len(t, pages, len(expected))
+ for i, number := range expected {
+ assert.Equal(t, number, pages[i].Num())
+ if number == current {
+ assert.True(t, pages[i].IsCurrent())
+ } else {
+ assert.False(t, pages[i].IsCurrent())
+ }
+ }
+ }
+
t.Run("Total page number is less or equal", func(t *testing.T) {
- p := New(1, 10, 1, 2)
- pages := p.Pages()
- assert.Len(t, pages, 1)
- assert.Equal(t, 1, pages[0].Num())
- assert.True(t, pages[0].IsCurrent())
+ assertPages(t, 1, 1, 2, 1)
- p = New(11, 10, 1, 2)
- pages = p.Pages()
- assert.Len(t, pages, 2)
- assert.Equal(t, 1, pages[0].Num())
- assert.True(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.False(t, pages[1].IsCurrent())
+ assertPages(t, 11, 1, 2, 1, 2)
- p = New(11, 10, 2, 2)
- pages = p.Pages()
- assert.Len(t, pages, 2)
- assert.Equal(t, 1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.True(t, pages[1].IsCurrent())
+ assertPages(t, 11, 2, 2, 1, 2)
- p = New(25, 10, 2, 3)
- pages = p.Pages()
- assert.Len(t, pages, 3)
- assert.Equal(t, 1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.True(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
+ assertPages(t, 25, 2, 3, 1, 2, 3)
})
t.Run("Has more previous pages ", func(t *testing.T) {
// ... 2
- p := New(11, 10, 2, 1)
- pages := p.Pages()
- assert.Len(t, pages, 2)
- assert.Equal(t, -1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.True(t, pages[1].IsCurrent())
+ assertPages(t, 11, 2, 1, -1, 2)
// ... 2 3
- p = New(21, 10, 2, 2)
- pages = p.Pages()
- assert.Len(t, pages, 3)
- assert.Equal(t, -1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.True(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
+ assertPages(t, 21, 2, 2, -1, 2, 3)
// ... 2 3 4
- p = New(31, 10, 3, 3)
- pages = p.Pages()
- assert.Len(t, pages, 4)
- assert.Equal(t, -1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.False(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.True(t, pages[2].IsCurrent())
- assert.Equal(t, 4, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
+ assertPages(t, 31, 3, 3, -1, 2, 3, 4)
// ... 3 4 5
- p = New(41, 10, 4, 3)
- pages = p.Pages()
- assert.Len(t, pages, 4)
- assert.Equal(t, -1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 3, pages[1].Num())
- assert.False(t, pages[1].IsCurrent())
- assert.Equal(t, 4, pages[2].Num())
- assert.True(t, pages[2].IsCurrent())
- assert.Equal(t, 5, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
+ assertPages(t, 41, 4, 3, -1, 3, 4, 5)
// ... 4 5 6 7 8 9 10
- p = New(100, 10, 9, 7)
- pages = p.Pages()
- assert.Len(t, pages, 8)
- assert.Equal(t, -1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 4, pages[1].Num())
- assert.False(t, pages[1].IsCurrent())
- assert.Equal(t, 5, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
- assert.Equal(t, 6, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
- assert.Equal(t, 7, pages[4].Num())
- assert.False(t, pages[4].IsCurrent())
- assert.Equal(t, 8, pages[5].Num())
- assert.False(t, pages[5].IsCurrent())
- assert.Equal(t, 9, pages[6].Num())
- assert.True(t, pages[6].IsCurrent())
- assert.Equal(t, 10, pages[7].Num())
- assert.False(t, pages[7].IsCurrent())
+ assertPages(t, 100, 9, 7, -1, 4, 5, 6, 7, 8, 9, 10)
})
t.Run("Has more next pages", func(t *testing.T) {
// 1 ...
- p := New(21, 10, 1, 1)
- pages := p.Pages()
- assert.Len(t, pages, 2)
- assert.Equal(t, 1, pages[0].Num())
- assert.True(t, pages[0].IsCurrent())
- assert.Equal(t, -1, pages[1].Num())
- assert.False(t, pages[1].IsCurrent())
+ assertPages(t, 21, 1, 1, 1, -1)
// 1 2 ...
- p = New(21, 10, 1, 2)
- pages = p.Pages()
- assert.Len(t, pages, 3)
- assert.Equal(t, 1, pages[0].Num())
- assert.True(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.False(t, pages[1].IsCurrent())
- assert.Equal(t, -1, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
+ assertPages(t, 21, 1, 2, 1, 2, -1)
// 1 2 3 ...
- p = New(31, 10, 2, 3)
- pages = p.Pages()
- assert.Len(t, pages, 4)
- assert.Equal(t, 1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.True(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
- assert.Equal(t, -1, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
+ assertPages(t, 31, 2, 3, 1, 2, 3, -1)
// 1 2 3 ...
- p = New(41, 10, 2, 3)
- pages = p.Pages()
- assert.Len(t, pages, 4)
- assert.Equal(t, 1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.True(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
- assert.Equal(t, -1, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
+ assertPages(t, 41, 2, 3, 1, 2, 3, -1)
// 1 2 3 4 5 6 7 ...
- p = New(100, 10, 1, 7)
- pages = p.Pages()
- assert.Len(t, pages, 8)
- assert.Equal(t, 1, pages[0].Num())
- assert.True(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.False(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
- assert.Equal(t, 4, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
- assert.Equal(t, 5, pages[4].Num())
- assert.False(t, pages[4].IsCurrent())
- assert.Equal(t, 6, pages[5].Num())
- assert.False(t, pages[5].IsCurrent())
- assert.Equal(t, 7, pages[6].Num())
- assert.False(t, pages[6].IsCurrent())
- assert.Equal(t, -1, pages[7].Num())
- assert.False(t, pages[7].IsCurrent())
+ assertPages(t, 100, 1, 7, 1, 2, 3, 4, 5, 6, 7, -1)
// 1 2 3 4 5 6 7 ...
- p = New(100, 10, 2, 7)
- pages = p.Pages()
- assert.Len(t, pages, 8)
- assert.Equal(t, 1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.True(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
- assert.Equal(t, 4, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
- assert.Equal(t, 5, pages[4].Num())
- assert.False(t, pages[4].IsCurrent())
- assert.Equal(t, 6, pages[5].Num())
- assert.False(t, pages[5].IsCurrent())
- assert.Equal(t, 7, pages[6].Num())
- assert.False(t, pages[6].IsCurrent())
- assert.Equal(t, -1, pages[7].Num())
- assert.False(t, pages[7].IsCurrent())
+ assertPages(t, 100, 2, 7, 1, 2, 3, 4, 5, 6, 7, -1)
})
t.Run("Has both more previous and next pages", func(t *testing.T) {
// ... 2 3 ...
- p := New(35, 10, 2, 2)
- pages := p.Pages()
- assert.Len(t, pages, 4)
- assert.Equal(t, -1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.True(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.False(t, pages[2].IsCurrent())
- assert.Equal(t, -1, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
+ assertPages(t, 35, 2, 2, -1, 2, 3, -1)
// ... 2 3 4 ...
- p = New(49, 10, 3, 3)
- pages = p.Pages()
- assert.Len(t, pages, 5)
- assert.Equal(t, -1, pages[0].Num())
- assert.False(t, pages[0].IsCurrent())
- assert.Equal(t, 2, pages[1].Num())
- assert.False(t, pages[1].IsCurrent())
- assert.Equal(t, 3, pages[2].Num())
- assert.True(t, pages[2].IsCurrent())
- assert.Equal(t, 4, pages[3].Num())
- assert.False(t, pages[3].IsCurrent())
- assert.Equal(t, -1, pages[4].Num())
- assert.False(t, pages[4].IsCurrent())
+ assertPages(t, 49, 3, 3, -1, 2, 3, 4, -1)
})
}
diff --git a/modules/process/manager_test.go b/modules/process/manager_test.go
index bf2e84bf1c0..6012ab61aee 100644
--- a/modules/process/manager_test.go
+++ b/modules/process/manager_test.go
@@ -41,29 +41,23 @@ func TestManager_AddContext(t *testing.T) {
func TestManager_Cancel(t *testing.T) {
pm := Manager{processMap: make(map[IDType]*process), next: 1}
- ctx, _, finished := pm.AddContext(t.Context(), "foo")
- defer finished()
+ for _, managerCancel := range []bool{true, false} {
+ ctx, cancel, finished := pm.AddContext(t.Context(), "foo")
+ defer finished()
- pm.Cancel(GetPID(ctx))
+ if managerCancel {
+ pm.Cancel(GetPID(ctx))
+ } else {
+ cancel()
+ }
- select {
- case <-ctx.Done():
- default:
- assert.FailNow(t, "Cancel should cancel the provided context")
+ select {
+ case <-ctx.Done():
+ default:
+ assert.FailNow(t, "Cancel should cancel the provided context")
+ }
+ finished()
}
- finished()
-
- ctx, cancel, finished := pm.AddContext(t.Context(), "foo")
- defer finished()
-
- cancel()
-
- select {
- case <-ctx.Done():
- default:
- assert.FailNow(t, "Cancel should cancel the provided context")
- }
- finished()
}
func TestManager_Remove(t *testing.T) {
diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go
index 5cadabdeac6..e85ec58a452 100644
--- a/modules/queue/workerqueue_test.go
+++ b/modules/queue/workerqueue_test.go
@@ -4,6 +4,7 @@
package queue
import (
+ "fmt"
"slices"
"strconv"
"sync"
@@ -76,40 +77,32 @@ func TestWorkerPoolQueueUnhandled(t *testing.T) {
}
runCount := 2 // we can run these tests even hundreds times to see its stability
- t.Run("1/1", func(t *testing.T) {
- for range runCount {
- test(t, setting.QueueSettings{BatchLength: 1, MaxWorkers: 1})
- }
- })
- t.Run("3/1", func(t *testing.T) {
- for range runCount {
- test(t, setting.QueueSettings{BatchLength: 3, MaxWorkers: 1})
- }
- })
- t.Run("4/5", func(t *testing.T) {
- for range runCount {
- test(t, setting.QueueSettings{BatchLength: 4, MaxWorkers: 5})
- }
- })
+ for _, queueSetting := range []setting.QueueSettings{
+ {BatchLength: 1, MaxWorkers: 1},
+ {BatchLength: 3, MaxWorkers: 1},
+ {BatchLength: 4, MaxWorkers: 5},
+ } {
+ t.Run(fmt.Sprintf("%d/%d", queueSetting.BatchLength, queueSetting.MaxWorkers), func(t *testing.T) {
+ for range runCount {
+ test(t, queueSetting)
+ }
+ })
+ }
}
func TestWorkerPoolQueuePersistence(t *testing.T) {
runCount := 2 // we can run these tests even hundreds times to see its stability
- t.Run("1/1", func(t *testing.T) {
- for range runCount {
- testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 1, MaxWorkers: 1, Length: 100})
- }
- })
- t.Run("3/1", func(t *testing.T) {
- for range runCount {
- testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 3, MaxWorkers: 1, Length: 100})
- }
- })
- t.Run("4/5", func(t *testing.T) {
- for range runCount {
- testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 4, MaxWorkers: 5, Length: 100})
- }
- })
+ for _, queueSetting := range []setting.QueueSettings{
+ {BatchLength: 1, MaxWorkers: 1, Length: 100},
+ {BatchLength: 3, MaxWorkers: 1, Length: 100},
+ {BatchLength: 4, MaxWorkers: 5, Length: 100},
+ } {
+ t.Run(fmt.Sprintf("%d/%d", queueSetting.BatchLength, queueSetting.MaxWorkers), func(t *testing.T) {
+ for range runCount {
+ testWorkerPoolQueuePersistence(t, queueSetting)
+ }
+ })
+ }
}
func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSettings) {
@@ -184,37 +177,23 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) {
return nil
}
- q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 1, Length: 100}, handler, false)
- stop := runWorkerPoolQueue(q)
- for i := range 5 {
- assert.NoError(t, q.Push(i))
+ for _, workerCount := range []int{1, 3} {
+ q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: workerCount, Length: 100}, handler, false)
+ stop := runWorkerPoolQueue(q)
+ for i := range workerCount * 5 {
+ assert.NoError(t, q.Push(i))
+ }
+
+ time.Sleep(50 * time.Millisecond)
+ assert.Equal(t, workerCount, q.GetWorkerNumber())
+ assert.Equal(t, workerCount, q.GetWorkerActiveNumber())
+ time.Sleep(500 * time.Millisecond)
+ assert.Equal(t, workerCount, q.GetWorkerNumber())
+ assert.Equal(t, 0, q.GetWorkerActiveNumber())
+ time.Sleep(workerIdleDuration)
+ assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working
+ stop()
}
-
- time.Sleep(50 * time.Millisecond)
- assert.Equal(t, 1, q.GetWorkerNumber())
- assert.Equal(t, 1, q.GetWorkerActiveNumber())
- time.Sleep(500 * time.Millisecond)
- assert.Equal(t, 1, q.GetWorkerNumber())
- assert.Equal(t, 0, q.GetWorkerActiveNumber())
- time.Sleep(workerIdleDuration)
- assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working
- stop()
-
- q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 3, Length: 100}, handler, false)
- stop = runWorkerPoolQueue(q)
- for i := range 15 {
- assert.NoError(t, q.Push(i))
- }
-
- time.Sleep(50 * time.Millisecond)
- assert.Equal(t, 3, q.GetWorkerNumber())
- assert.Equal(t, 3, q.GetWorkerActiveNumber())
- time.Sleep(500 * time.Millisecond)
- assert.Equal(t, 3, q.GetWorkerNumber())
- assert.Equal(t, 0, q.GetWorkerActiveNumber())
- time.Sleep(workerIdleDuration)
- assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working
- stop()
}
func TestWorkerPoolQueueShutdown(t *testing.T) {
diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go
index 5e6266d9f21..dfe3093ace6 100644
--- a/modules/repository/commits_test.go
+++ b/modules/repository/commits_test.go
@@ -10,6 +10,7 @@ import (
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
"gitea.dev/modules/git"
+ api "gitea.dev/modules/structs"
"github.com/stretchr/testify/assert"
)
@@ -52,49 +53,27 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
assert.Len(t, payloadCommits, 3)
assert.NotNil(t, headCommit)
- assert.Equal(t, "69554a6", payloadCommits[0].ID)
- assert.Equal(t, "not signed commit", payloadCommits[0].Message)
- assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/69554a6", payloadCommits[0].URL)
- assert.Equal(t, "User2", payloadCommits[0].Committer.Name)
- assert.Equal(t, "user2", payloadCommits[0].Committer.UserName)
- assert.Equal(t, "User2", payloadCommits[0].Author.Name)
- assert.Equal(t, "user2", payloadCommits[0].Author.UserName)
- assert.Equal(t, []string{}, payloadCommits[0].Added)
- assert.Equal(t, []string{}, payloadCommits[0].Removed)
- assert.Equal(t, []string{"readme.md"}, payloadCommits[0].Modified)
-
- assert.Equal(t, "27566bd", payloadCommits[1].ID)
- assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message)
- assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/27566bd", payloadCommits[1].URL)
- assert.Equal(t, "User2", payloadCommits[1].Committer.Name)
- assert.Equal(t, "user2", payloadCommits[1].Committer.UserName)
- assert.Equal(t, "User2", payloadCommits[1].Author.Name)
- assert.Equal(t, "user2", payloadCommits[1].Author.UserName)
- assert.Equal(t, []string{}, payloadCommits[1].Added)
- assert.Equal(t, []string{}, payloadCommits[1].Removed)
- assert.Equal(t, []string{"readme.md"}, payloadCommits[1].Modified)
-
- assert.Equal(t, "5099b81", payloadCommits[2].ID)
- assert.Equal(t, "good signed commit", payloadCommits[2].Message)
- assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/5099b81", payloadCommits[2].URL)
- assert.Equal(t, "User2", payloadCommits[2].Committer.Name)
- assert.Equal(t, "user2", payloadCommits[2].Committer.UserName)
- assert.Equal(t, "User2", payloadCommits[2].Author.Name)
- assert.Equal(t, "user2", payloadCommits[2].Author.UserName)
- assert.Equal(t, []string{"readme.md"}, payloadCommits[2].Added)
- assert.Equal(t, []string{}, payloadCommits[2].Removed)
- assert.Equal(t, []string{}, payloadCommits[2].Modified)
-
- assert.Equal(t, "69554a6", headCommit.ID)
- assert.Equal(t, "not signed commit", headCommit.Message)
- assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/69554a6", headCommit.URL)
- assert.Equal(t, "User2", headCommit.Committer.Name)
- assert.Equal(t, "user2", headCommit.Committer.UserName)
- assert.Equal(t, "User2", headCommit.Author.Name)
- assert.Equal(t, "user2", headCommit.Author.UserName)
- assert.Equal(t, []string{}, headCommit.Added)
- assert.Equal(t, []string{}, headCommit.Removed)
- assert.Equal(t, []string{"readme.md"}, headCommit.Modified)
+ for _, tt := range []struct {
+ commit *api.PayloadCommit
+ id, message, url string
+ added, modified []string
+ }{
+ {payloadCommits[0], "69554a6", "not signed commit", "https://try.gitea.io/user2/repo16/commit/69554a6", []string{}, []string{"readme.md"}},
+ {payloadCommits[1], "27566bd", "good signed commit (with not yet validated email)", "https://try.gitea.io/user2/repo16/commit/27566bd", []string{}, []string{"readme.md"}},
+ {payloadCommits[2], "5099b81", "good signed commit", "https://try.gitea.io/user2/repo16/commit/5099b81", []string{"readme.md"}, []string{}},
+ {headCommit, "69554a6", "not signed commit", "https://try.gitea.io/user2/repo16/commit/69554a6", []string{}, []string{"readme.md"}},
+ } {
+ assert.Equal(t, tt.id, tt.commit.ID)
+ assert.Equal(t, tt.message, tt.commit.Message)
+ assert.Equal(t, tt.url, tt.commit.URL)
+ assert.Equal(t, "User2", tt.commit.Committer.Name)
+ assert.Equal(t, "user2", tt.commit.Committer.UserName)
+ assert.Equal(t, "User2", tt.commit.Author.Name)
+ assert.Equal(t, "user2", tt.commit.Author.UserName)
+ assert.Equal(t, tt.added, tt.commit.Added)
+ assert.Equal(t, []string{}, tt.commit.Removed)
+ assert.Equal(t, tt.modified, tt.commit.Modified)
+ }
}
func TestCommitToPushCommit(t *testing.T) {
diff --git a/modules/repository/license_test.go b/modules/repository/license_test.go
index d00156a496a..c1b94a55c93 100644
--- a/modules/repository/license_test.go
+++ b/modules/repository/license_test.go
@@ -11,22 +11,17 @@ import (
)
func Test_getLicense(t *testing.T) {
- type args struct {
- name string
- values *LicenseValues
- }
tests := []struct {
name string
- args args
+ license string
+ values *LicenseValues
want string
wantErr assert.ErrorAssertionFunc
}{
{
- name: "regular",
- args: args{
- name: "MIT",
- values: &LicenseValues{Owner: "Gitea", Year: "2023"},
- },
+ name: "regular",
+ license: "MIT",
+ values: &LicenseValues{Owner: "Gitea", Year: "2023"},
want: `MIT License
Copyright (c) 2023 Gitea
@@ -35,41 +30,33 @@ Permission is hereby granted`,
wantErr: assert.NoError,
},
{
- name: "license not found",
- args: args{
- name: "notfound",
- },
+ name: "license not found",
+ license: "notfound",
wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got, err := GetLicense(tt.args.name, tt.args.values)
- if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.args.name, tt.args.values)) {
+ got, err := GetLicense(tt.license, tt.values)
+ if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.license, tt.values)) {
return
}
- assert.Contains(t, string(got), tt.want, "GetLicense(%v, %v)", tt.args.name, tt.args.values)
+ assert.Contains(t, string(got), tt.want, "GetLicense(%v, %v)", tt.license, tt.values)
})
}
}
func Test_fillLicensePlaceholder(t *testing.T) {
- type args struct {
- name string
- values *LicenseValues
- origin string
- }
tests := []struct {
- name string
- args args
- want string
+ name string
+ license string
+ origin string
+ want string
}{
{
- name: "owner",
- args: args{
- name: "regular",
- values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
- origin: `
+ name: "owner",
+ license: "regular",
+ origin: `
[NAME]
@@ -81,7 +68,6 @@ func Test_fillLicensePlaceholder(t *testing.T) {
[one or more legally recognised persons or entities offering the Work under the terms and conditions of this Licence]
`,
- },
want: `
Gitea
Gitea
@@ -96,39 +82,31 @@ Gitea
`,
},
{
- name: "email",
- args: args{
- name: "regular",
- values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
- origin: `
+ name: "email",
+ license: "regular",
+ origin: `
[EMAIL]
`,
- },
want: `
teabot@gitea.io
`,
},
{
- name: "repo",
- args: args{
- name: "regular",
- values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
- origin: `
+ name: "repo",
+ license: "regular",
+ origin: `
`,
- },
want: `
gitea
gitea
`,
},
{
- name: "year",
- args: args{
- name: "regular",
- values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
- origin: `
+ name: "year",
+ license: "regular",
+ origin: `
[YEAR]
{YEAR}
@@ -136,7 +114,6 @@ gitea
[Year]
[year]
`,
- },
want: `
2023
2023
@@ -147,18 +124,15 @@ gitea
`,
},
{
- name: "0BSD",
- args: args{
- name: "0BSD",
- values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
- origin: `
+ name: "0BSD",
+ license: "0BSD",
+ origin: `
Copyright (C) YEAR by AUTHOR EMAIL
...
... THE AUTHOR BE LIABLE FOR ...
`,
- },
want: `
Copyright (C) 2023 by Gitea teabot@gitea.io
@@ -170,7 +144,8 @@ Copyright (C) 2023 by Gitea teabot@gitea.io
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- assert.Equalf(t, tt.want, string(fillLicensePlaceholder(tt.args.name, tt.args.values, []byte(tt.args.origin))), "fillLicensePlaceholder(%v, %v, %v)", tt.args.name, tt.args.values, tt.args.origin)
+ values := &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}
+ assert.Equalf(t, tt.want, string(fillLicensePlaceholder(tt.license, values, []byte(tt.origin))), "fillLicensePlaceholder(%v, %v, %v)", tt.license, values, tt.origin)
})
}
}
diff --git a/modules/setting/actions_test.go b/modules/setting/actions_test.go
index 5b2e5ac3d9f..e5f8cd30768 100644
--- a/modules/setting/actions_test.go
+++ b/modules/setting/actions_test.go
@@ -15,89 +15,57 @@ import (
)
func Test_getStorageInheritNameSectionTypeForActions(t *testing.T) {
- iniStr := `
+ for _, tc := range []struct {
+ iniStr, logType, artifactType string
+ }{
+ {`
[storage]
STORAGE_TYPE = minio
- `
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadActionsFrom(cfg))
-
- assert.EqualValues(t, "minio", Actions.LogStorage.Type)
- assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath)
- assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type)
- assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath)
-
- iniStr = `
+ `, "minio", "minio"},
+ {`
[storage.actions_log]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadActionsFrom(cfg))
-
- assert.EqualValues(t, "minio", Actions.LogStorage.Type)
- assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath)
- assert.EqualValues(t, "local", Actions.ArtifactStorage.Type)
- assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path))
-
- iniStr = `
+`, "minio", "local"},
+ {`
[storage.actions_log]
STORAGE_TYPE = my_storage
[storage.my_storage]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadActionsFrom(cfg))
-
- assert.EqualValues(t, "minio", Actions.LogStorage.Type)
- assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath)
- assert.EqualValues(t, "local", Actions.ArtifactStorage.Type)
- assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path))
-
- iniStr = `
+`, "minio", "local"},
+ {`
[storage.actions_artifacts]
STORAGE_TYPE = my_storage
[storage.my_storage]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadActionsFrom(cfg))
-
- assert.EqualValues(t, "local", Actions.LogStorage.Type)
- assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path))
- assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type)
- assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath)
-
- iniStr = `
+`, "local", "minio"},
+ {`
[storage.actions_artifacts]
STORAGE_TYPE = my_storage
[storage.my_storage]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadActionsFrom(cfg))
+`, "local", "minio"},
+ {``, "local", "local"},
+ } {
+ cfg, err := NewConfigProviderFromData(tc.iniStr)
+ assert.NoError(t, err)
+ assert.NoError(t, loadActionsFrom(cfg))
- assert.EqualValues(t, "local", Actions.LogStorage.Type)
- assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path))
- assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type)
- assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath)
-
- iniStr = ``
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadActionsFrom(cfg))
-
- assert.EqualValues(t, "local", Actions.LogStorage.Type)
- assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path))
- assert.EqualValues(t, "local", Actions.ArtifactStorage.Type)
- assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path))
+ assert.EqualValues(t, tc.logType, Actions.LogStorage.Type)
+ if tc.logType == "minio" {
+ assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath)
+ } else {
+ assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path))
+ }
+ assert.EqualValues(t, tc.artifactType, Actions.ArtifactStorage.Type)
+ if tc.artifactType == "minio" {
+ assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath)
+ } else {
+ assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path))
+ }
+ }
}
func Test_WorkflowDirs(t *testing.T) {
diff --git a/modules/setting/attachment_test.go b/modules/setting/attachment_test.go
index c566dfa60cd..74a088e7bf5 100644
--- a/modules/setting/attachment_test.go
+++ b/modules/setting/attachment_test.go
@@ -10,7 +10,10 @@ import (
)
func Test_getStorageCustomType(t *testing.T) {
- iniStr := `
+ for _, tc := range []struct {
+ iniStr, endpoint, bucket string
+ }{
+ {`
[attachment]
STORAGE_TYPE = my_minio
MINIO_BUCKET = gitea-attachment
@@ -18,20 +21,8 @@ MINIO_BUCKET = gitea-attachment
[storage.my_minio]
STORAGE_TYPE = minio
MINIO_ENDPOINT = my_minio:9000
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
-
- assert.NoError(t, loadAttachmentFrom(cfg))
-
- assert.EqualValues(t, "minio", Attachment.Storage.Type)
- assert.Equal(t, "my_minio:9000", Attachment.Storage.MinioConfig.Endpoint)
- assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket)
- assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
-}
-
-func Test_getStorageTypeSectionOverridesStorageSection(t *testing.T) {
- iniStr := `
+`, "my_minio:9000", "gitea-attachment"},
+ {`
[attachment]
STORAGE_TYPE = minio
@@ -40,19 +31,8 @@ MINIO_BUCKET = gitea-minio
[storage]
MINIO_BUCKET = gitea
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
-
- assert.NoError(t, loadAttachmentFrom(cfg))
-
- assert.EqualValues(t, "minio", Attachment.Storage.Type)
- assert.Equal(t, "gitea-minio", Attachment.Storage.MinioConfig.Bucket)
- assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
-}
-
-func Test_getStorageSpecificOverridesStorage(t *testing.T) {
- iniStr := `
+`, "", "gitea-minio"},
+ {`
[attachment]
STORAGE_TYPE = minio
MINIO_BUCKET = gitea-attachment
@@ -62,15 +42,19 @@ MINIO_BUCKET = gitea
[storage]
STORAGE_TYPE = local
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
+`, "", "gitea-attachment"},
+ } {
+ cfg, err := NewConfigProviderFromData(tc.iniStr)
+ assert.NoError(t, err)
+ assert.NoError(t, loadAttachmentFrom(cfg))
- assert.NoError(t, loadAttachmentFrom(cfg))
-
- assert.EqualValues(t, "minio", Attachment.Storage.Type)
- assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket)
- assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
+ assert.EqualValues(t, "minio", Attachment.Storage.Type)
+ if tc.endpoint != "" {
+ assert.Equal(t, tc.endpoint, Attachment.Storage.MinioConfig.Endpoint)
+ }
+ assert.Equal(t, tc.bucket, Attachment.Storage.MinioConfig.Bucket)
+ assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
+ }
}
func Test_getStorageGetDefaults(t *testing.T) {
diff --git a/modules/setting/config_env_test.go b/modules/setting/config_env_test.go
index 7d270ac21ad..d8c83220841 100644
--- a/modules/setting/config_env_test.go
+++ b/modules/setting/config_env_test.go
@@ -11,22 +11,20 @@ import (
)
func TestDecodeEnvSectionKey(t *testing.T) {
- ok, section, key := decodeEnvSectionKey("SEC__KEY")
- assert.True(t, ok)
- assert.Equal(t, "sec", section)
- assert.Equal(t, "KEY", key)
+ for _, tc := range []struct {
+ input, section, key string
+ }{
+ {"SEC__KEY", "sec", "KEY"},
+ {"sec__key", "sec", "key"},
+ {"LOG_0x2E_CONSOLE__STDERR", "log.console", "STDERR"},
+ } {
+ ok, section, key := decodeEnvSectionKey(tc.input)
+ assert.True(t, ok)
+ assert.Equal(t, tc.section, section)
+ assert.Equal(t, tc.key, key)
+ }
- ok, section, key = decodeEnvSectionKey("sec__key")
- assert.True(t, ok)
- assert.Equal(t, "sec", section)
- assert.Equal(t, "key", key)
-
- ok, section, key = decodeEnvSectionKey("LOG_0x2E_CONSOLE__STDERR")
- assert.True(t, ok)
- assert.Equal(t, "log.console", section)
- assert.Equal(t, "STDERR", key)
-
- ok, section, key = decodeEnvSectionKey("SEC")
+ ok, section, key := decodeEnvSectionKey("SEC")
assert.False(t, ok)
assert.Empty(t, section)
assert.Empty(t, key)
@@ -36,19 +34,15 @@ func TestDecodeEnvironmentKey(t *testing.T) {
prefix := "GITEA__"
suffix := "__FILE"
- ok, section, key, file := decodeEnvironmentKey(prefix, suffix, "SEC__KEY")
- assert.False(t, ok)
- assert.Empty(t, section)
- assert.Empty(t, key)
- assert.False(t, file)
+ for _, input := range []string{"SEC__KEY", "GITEA__SEC"} {
+ ok, section, key, file := decodeEnvironmentKey(prefix, suffix, input)
+ assert.False(t, ok)
+ assert.Empty(t, section)
+ assert.Empty(t, key)
+ assert.False(t, file)
+ }
- ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA__SEC")
- assert.False(t, ok)
- assert.Empty(t, section)
- assert.Empty(t, key)
- assert.False(t, file)
-
- ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA____KEY")
+ ok, section, key, file := decodeEnvironmentKey(prefix, suffix, "GITEA____KEY")
assert.True(t, ok)
assert.Empty(t, section)
assert.Equal(t, "KEY", key)
@@ -103,20 +97,18 @@ key = old
assert.True(t, changed)
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
- cfg, _ = NewConfigProviderFromData("")
- _ = os.WriteFile(tmpFile, []byte("value-from-file\n"), 0o644)
- EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
- assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
-
- cfg, _ = NewConfigProviderFromData("")
- _ = os.WriteFile(tmpFile, []byte("value-from-file\r\n"), 0o644)
- EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
- assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
-
- cfg, _ = NewConfigProviderFromData("")
- _ = os.WriteFile(tmpFile, []byte("value-from-file\n\n"), 0o644)
- EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
- assert.Equal(t, "value-from-file\n", cfg.Section("sec").Key("key").String())
+ for _, tc := range []struct {
+ content, want string
+ }{
+ {"value-from-file\n", "value-from-file"},
+ {"value-from-file\r\n", "value-from-file"},
+ {"value-from-file\n\n", "value-from-file\n"},
+ } {
+ cfg, _ = NewConfigProviderFromData("")
+ _ = os.WriteFile(tmpFile, []byte(tc.content), 0o644)
+ EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
+ assert.Equal(t, tc.want, cfg.Section("sec").Key("key").String())
+ }
}
func TestEnvironmentToConfigSubSecKey(t *testing.T) {
diff --git a/modules/setting/global_lock_test.go b/modules/setting/global_lock_test.go
index 22bb4233f3d..6ff3e5abcbe 100644
--- a/modules/setting/global_lock_test.go
+++ b/modules/setting/global_lock_test.go
@@ -12,63 +12,39 @@ import (
)
func TestLoadGlobalLockConfig(t *testing.T) {
- t.Run("DefaultGlobalLockConfig", func(t *testing.T) {
- defer test.MockVariableValue(&Redis)()
- iniStr := ``
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- loadRedisFrom(cfg)
- loadGlobalLockFrom(cfg)
- assert.Equal(t, "memory", GlobalLock.ServiceType)
- })
-
- t.Run("RedisGlobalLockConfig", func(t *testing.T) {
- defer test.MockVariableValue(&Redis)()
- iniStr := `
+ for _, tt := range []struct {
+ name, config, serviceType, connStr string
+ }{
+ {"DefaultGlobalLockConfig", ``, "memory", ""},
+ {"RedisGlobalLockConfig", `
[global_lock]
SERVICE_TYPE = redis
SERVICE_CONN_STR = addrs=127.0.0.1:6379 db=0
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- loadRedisFrom(cfg)
- loadGlobalLockFrom(cfg)
- assert.Equal(t, "redis", GlobalLock.ServiceType)
- assert.Equal(t, "addrs=127.0.0.1:6379 db=0", GlobalLock.ServiceConnStr)
- })
-
- t.Run("RedisGlobalLockFallsBackToSharedRedis", func(t *testing.T) {
- defer test.MockVariableValue(&Redis)()
- iniStr := `
+`, "redis", "addrs=127.0.0.1:6379 db=0"},
+ {"RedisGlobalLockFallsBackToSharedRedis", `
[redis]
CONN_STR = redis://127.0.0.1:6379/0
[global_lock]
SERVICE_TYPE = redis
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
-
- loadRedisFrom(cfg)
- loadGlobalLockFrom(cfg)
- assert.Equal(t, "redis", GlobalLock.ServiceType)
- assert.Equal(t, "redis://127.0.0.1:6379/0", GlobalLock.ServiceConnStr)
- })
-
- t.Run("RedisGlobalLockOwnConnWinsOverSharedRedis", func(t *testing.T) {
- defer test.MockVariableValue(&Redis)()
- iniStr := `
+`, "redis", "redis://127.0.0.1:6379/0"},
+ {"RedisGlobalLockOwnConnWinsOverSharedRedis", `
[redis]
CONN_STR = redis://127.0.0.1:6379/0
[global_lock]
SERVICE_TYPE = redis
SERVICE_CONN_STR = redis://10.0.0.1:6379/1
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
-
- loadRedisFrom(cfg)
- loadGlobalLockFrom(cfg)
- assert.Equal(t, "redis", GlobalLock.ServiceType)
- assert.Equal(t, "redis://10.0.0.1:6379/1", GlobalLock.ServiceConnStr)
- })
+`, "redis", "redis://10.0.0.1:6379/1"},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ defer test.MockVariableValue(&Redis)()
+ cfg, err := NewConfigProviderFromData(tt.config)
+ assert.NoError(t, err)
+ loadRedisFrom(cfg)
+ loadGlobalLockFrom(cfg)
+ assert.Equal(t, tt.serviceType, GlobalLock.ServiceType)
+ if tt.serviceType == "redis" {
+ assert.Equal(t, tt.connStr, GlobalLock.ServiceConnStr)
+ }
+ })
+ }
}
diff --git a/modules/setting/lfs_test.go b/modules/setting/lfs_test.go
index faab84d1aa6..62e8eebff0d 100644
--- a/modules/setting/lfs_test.go
+++ b/modules/setting/lfs_test.go
@@ -12,82 +12,56 @@ import (
)
func Test_getStorageInheritNameSectionTypeForLFS(t *testing.T) {
- iniStr := `
+ for _, tt := range []struct {
+ config, storageType, path string
+ }{
+ {`
[storage]
STORAGE_TYPE = minio
- `
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadLFSFrom(cfg))
-
- assert.EqualValues(t, "minio", LFS.Storage.Type)
- assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
-
- iniStr = `
+ `, "minio", "lfs/"},
+ {`
[server]
LFS_CONTENT_PATH = path_ignored
[lfs]
PATH = path_used
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- applyDeprecatedKeys(cfg)
- assert.NoError(t, loadLFSFrom(cfg))
-
- assert.EqualValues(t, "local", LFS.Storage.Type)
- assert.Contains(t, LFS.Storage.Path, "path_used")
-
- iniStr = `
+`, "local", "path_used"},
+ {`
[server]
LFS_CONTENT_PATH = deprecatedpath
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- applyDeprecatedKeys(cfg)
- assert.NoError(t, loadLFSFrom(cfg))
-
- assert.EqualValues(t, "local", LFS.Storage.Type)
- assert.Contains(t, LFS.Storage.Path, "deprecatedpath")
-
- iniStr = `
+`, "local", "deprecatedpath"},
+ {`
[storage.lfs]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadLFSFrom(cfg))
-
- assert.EqualValues(t, "minio", LFS.Storage.Type)
- assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
-
- iniStr = `
+`, "minio", "lfs/"},
+ {`
[lfs]
STORAGE_TYPE = my_minio
[storage.my_minio]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadLFSFrom(cfg))
-
- assert.EqualValues(t, "minio", LFS.Storage.Type)
- assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
-
- iniStr = `
+`, "minio", "lfs/"},
+ {`
[lfs]
STORAGE_TYPE = my_minio
MINIO_BASE_PATH = my_lfs/
[storage.my_minio]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadLFSFrom(cfg))
-
- assert.EqualValues(t, "minio", LFS.Storage.Type)
- assert.Equal(t, "my_lfs/", LFS.Storage.MinioConfig.BasePath)
+`, "minio", "my_lfs/"},
+ } {
+ cfg, err := NewConfigProviderFromData(tt.config)
+ assert.NoError(t, err)
+ if tt.storageType == "local" {
+ applyDeprecatedKeys(cfg)
+ }
+ assert.NoError(t, loadLFSFrom(cfg))
+ assert.EqualValues(t, tt.storageType, LFS.Storage.Type)
+ if tt.storageType == "local" {
+ assert.Contains(t, LFS.Storage.Path, tt.path)
+ } else {
+ assert.Equal(t, tt.path, LFS.Storage.MinioConfig.BasePath)
+ }
+ }
}
func Test_LFSStorage1(t *testing.T) {
diff --git a/modules/setting/log_test.go b/modules/setting/log_test.go
index a217cf5490c..17b5b31b83f 100644
--- a/modules/setting/log_test.go
+++ b/modules/setting/log_test.go
@@ -36,9 +36,6 @@ func toJSON(v any) string {
}
func TestLogConfigDefault(t *testing.T) {
- manager, managerClose := initLoggersByConfig(t, ``)
- defer managerClose()
-
writerDump := `
{
"console": {
@@ -57,95 +54,37 @@ func TestLogConfigDefault(t *testing.T) {
}
`
- dump := manager.GetLogger(log.DEFAULT).DumpWriters()
- require.JSONEq(t, writerDump, toJSON(dump))
-
- dump = manager.GetLogger("access").DumpWriters()
- require.JSONEq(t, "{}", toJSON(dump))
-
- dump = manager.GetLogger("router").DumpWriters()
- require.JSONEq(t, writerDump, toJSON(dump))
-
- dump = manager.GetLogger("xorm").DumpWriters()
- require.JSONEq(t, writerDump, toJSON(dump))
-}
-
-func TestLogConfigDisable(t *testing.T) {
- manager, managerClose := initLoggersByConfig(t, `
+ for _, tt := range []struct {
+ name, config, routerDump string
+ }{
+ {"Default", ``, writerDump},
+ {"Disable", `
[log]
logger.router.MODE =
logger.xorm.MODE =
-`)
- defer managerClose()
-
- writerDump := `
-{
- "console": {
- "BufferLen": 10000,
- "Colorize": false,
- "Expression": "",
- "Flags": "stdflags",
- "Level": "info",
- "Prefix": "",
- "StacktraceLevel": "none",
- "WriterOption": {
- "Stderr": false
- },
- "WriterType": "console"
- }
-}
-`
-
- dump := manager.GetLogger(log.DEFAULT).DumpWriters()
- require.JSONEq(t, writerDump, toJSON(dump))
-
- dump = manager.GetLogger("access").DumpWriters()
- require.JSONEq(t, "{}", toJSON(dump))
-
- dump = manager.GetLogger("router").DumpWriters()
- require.JSONEq(t, "{}", toJSON(dump))
-
- dump = manager.GetLogger("xorm").DumpWriters()
- require.JSONEq(t, "{}", toJSON(dump))
-}
-
-func TestLogConfigLegacyDefault(t *testing.T) {
- manager, managerClose := initLoggersByConfig(t, `
+`, "{}"},
+ {"LegacyDefault", `
[log]
MODE = console
-`)
- defer managerClose()
+`, writerDump},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ manager, managerClose := initLoggersByConfig(t, tt.config)
+ defer managerClose()
- writerDump := `
-{
- "console": {
- "BufferLen": 10000,
- "Colorize": false,
- "Expression": "",
- "Flags": "stdflags",
- "Level": "info",
- "Prefix": "",
- "StacktraceLevel": "none",
- "WriterOption": {
- "Stderr": false
- },
- "WriterType": "console"
+ for _, logger := range []struct {
+ name, expected string
+ }{
+ {log.DEFAULT, writerDump},
+ {"access", "{}"},
+ {"router", tt.routerDump},
+ {"xorm", tt.routerDump},
+ } {
+ require.JSONEq(t, logger.expected, toJSON(manager.GetLogger(logger.name).DumpWriters()))
+ }
+ })
}
}
-`
-
- dump := manager.GetLogger(log.DEFAULT).DumpWriters()
- require.JSONEq(t, writerDump, toJSON(dump))
-
- dump = manager.GetLogger("access").DumpWriters()
- require.JSONEq(t, "{}", toJSON(dump))
-
- dump = manager.GetLogger("router").DumpWriters()
- require.JSONEq(t, writerDump, toJSON(dump))
-
- dump = manager.GetLogger("xorm").DumpWriters()
- require.JSONEq(t, writerDump, toJSON(dump))
-}
func TestLogConfigLegacyMode(t *testing.T) {
tempDir := t.TempDir()
diff --git a/modules/setting/packages_test.go b/modules/setting/packages_test.go
index 47378f35add..ea7fe5a74ea 100644
--- a/modules/setting/packages_test.go
+++ b/modules/setting/packages_test.go
@@ -31,168 +31,98 @@ func TestMustBytes(t *testing.T) {
}
func Test_getStorageInheritNameSectionTypeForPackages(t *testing.T) {
- // packages storage inherits from storage if nothing configured
- iniStr := `
+ for _, testcase := range []struct {
+ iniStr, basePath string
+ }{
+ // packages storage inherits from storage if nothing configured
+ {`
[storage]
STORAGE_TYPE = minio
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadPackagesFrom(cfg))
-
- assert.EqualValues(t, "minio", Packages.Storage.Type)
- assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath)
-
- // we can also configure packages storage directly
- iniStr = `
+`, "packages/"},
+ // we can also configure packages storage directly
+ {`
[storage.packages]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadPackagesFrom(cfg))
-
- assert.EqualValues(t, "minio", Packages.Storage.Type)
- assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath)
-
- // or we can indicate the storage type in the packages section
- iniStr = `
+`, "packages/"},
+ // or we can indicate the storage type in the packages section
+ {`
[packages]
STORAGE_TYPE = my_minio
[storage.my_minio]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadPackagesFrom(cfg))
-
- assert.EqualValues(t, "minio", Packages.Storage.Type)
- assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath)
-
- // or we can indicate the storage type and minio base path in the packages section
- iniStr = `
+`, "packages/"},
+ // or we can indicate the storage type and minio base path in the packages section
+ {`
[packages]
STORAGE_TYPE = my_minio
MINIO_BASE_PATH = my_packages/
[storage.my_minio]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadPackagesFrom(cfg))
+`, "my_packages/"},
+ } {
+ cfg, err := NewConfigProviderFromData(testcase.iniStr)
+ assert.NoError(t, err)
+ assert.NoError(t, loadPackagesFrom(cfg))
- assert.EqualValues(t, "minio", Packages.Storage.Type)
- assert.Equal(t, "my_packages/", Packages.Storage.MinioConfig.BasePath)
+ assert.EqualValues(t, "minio", Packages.Storage.Type)
+ assert.Equal(t, testcase.basePath, Packages.Storage.MinioConfig.BasePath)
+ }
}
-func Test_PackageStorage1(t *testing.T) {
- iniStr := `
+func Test_PackageStorage(t *testing.T) {
+ const storageConfig = `STORAGE_TYPE = minio
+MINIO_ENDPOINT = s3.my-domain.net
+MINIO_BUCKET = gitea
+MINIO_LOCATION = homenet
+MINIO_USE_SSL = true
+MINIO_ACCESS_KEY_ID = correct_key
+MINIO_SECRET_ACCESS_KEY = correct_key
+`
+ for _, testcase := range []struct {
+ iniStr, basePath string
+ }{
+ {`
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[packages]
MINIO_BASE_PATH = packages/
SERVE_DIRECT = true
[storage]
-STORAGE_TYPE = minio
-MINIO_ENDPOINT = s3.my-domain.net
-MINIO_BUCKET = gitea
-MINIO_LOCATION = homenet
-MINIO_USE_SSL = true
-MINIO_ACCESS_KEY_ID = correct_key
-MINIO_SECRET_ACCESS_KEY = correct_key
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
-
- assert.NoError(t, loadPackagesFrom(cfg))
- storage := Packages.Storage
-
- assert.EqualValues(t, "minio", storage.Type)
- assert.Equal(t, "gitea", storage.MinioConfig.Bucket)
- assert.Equal(t, "packages/", storage.MinioConfig.BasePath)
- assert.True(t, storage.MinioConfig.ServeDirect)
-}
-
-func Test_PackageStorage2(t *testing.T) {
- iniStr := `
+`, "packages/"},
+ {`
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[storage.packages]
MINIO_BASE_PATH = packages/
SERVE_DIRECT = true
[storage]
-STORAGE_TYPE = minio
-MINIO_ENDPOINT = s3.my-domain.net
-MINIO_BUCKET = gitea
-MINIO_LOCATION = homenet
-MINIO_USE_SSL = true
-MINIO_ACCESS_KEY_ID = correct_key
-MINIO_SECRET_ACCESS_KEY = correct_key
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
-
- assert.NoError(t, loadPackagesFrom(cfg))
- storage := Packages.Storage
-
- assert.EqualValues(t, "minio", storage.Type)
- assert.Equal(t, "gitea", storage.MinioConfig.Bucket)
- assert.Equal(t, "packages/", storage.MinioConfig.BasePath)
- assert.True(t, storage.MinioConfig.ServeDirect)
-}
-
-func Test_PackageStorage3(t *testing.T) {
- iniStr := `
+`, "packages/"},
+ {`
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[packages]
STORAGE_TYPE = my_cfg
MINIO_BASE_PATH = my_packages/
SERVE_DIRECT = true
[storage.my_cfg]
-STORAGE_TYPE = minio
-MINIO_ENDPOINT = s3.my-domain.net
-MINIO_BUCKET = gitea
-MINIO_LOCATION = homenet
-MINIO_USE_SSL = true
-MINIO_ACCESS_KEY_ID = correct_key
-MINIO_SECRET_ACCESS_KEY = correct_key
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
-
- assert.NoError(t, loadPackagesFrom(cfg))
- storage := Packages.Storage
-
- assert.EqualValues(t, "minio", storage.Type)
- assert.Equal(t, "gitea", storage.MinioConfig.Bucket)
- assert.Equal(t, "my_packages/", storage.MinioConfig.BasePath)
- assert.True(t, storage.MinioConfig.ServeDirect)
-}
-
-func Test_PackageStorage4(t *testing.T) {
- iniStr := `
+`, "my_packages/"},
+ {`
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[storage.packages]
STORAGE_TYPE = my_cfg
MINIO_BASE_PATH = my_packages/
SERVE_DIRECT = true
[storage.my_cfg]
-STORAGE_TYPE = minio
-MINIO_ENDPOINT = s3.my-domain.net
-MINIO_BUCKET = gitea
-MINIO_LOCATION = homenet
-MINIO_USE_SSL = true
-MINIO_ACCESS_KEY_ID = correct_key
-MINIO_SECRET_ACCESS_KEY = correct_key
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
+`, "my_packages/"},
+ } {
+ cfg, err := NewConfigProviderFromData(testcase.iniStr + storageConfig)
+ assert.NoError(t, err)
- assert.NoError(t, loadPackagesFrom(cfg))
- storage := Packages.Storage
+ assert.NoError(t, loadPackagesFrom(cfg))
+ storage := Packages.Storage
- assert.EqualValues(t, "minio", storage.Type)
- assert.Equal(t, "gitea", storage.MinioConfig.Bucket)
- assert.Equal(t, "my_packages/", storage.MinioConfig.BasePath)
- assert.True(t, storage.MinioConfig.ServeDirect)
+ assert.EqualValues(t, "minio", storage.Type)
+ assert.Equal(t, "gitea", storage.MinioConfig.Bucket)
+ assert.Equal(t, testcase.basePath, storage.MinioConfig.BasePath)
+ assert.True(t, storage.MinioConfig.ServeDirect)
+ }
}
diff --git a/modules/setting/repository_archive_test.go b/modules/setting/repository_archive_test.go
index d5b95272d63..1fc639b80a4 100644
--- a/modules/setting/repository_archive_test.go
+++ b/modules/setting/repository_archive_test.go
@@ -10,64 +10,49 @@ import (
)
func Test_getStorageInheritNameSectionTypeForRepoArchive(t *testing.T) {
- // packages storage inherits from storage if nothing configured
- iniStr := `
+ for _, tc := range []struct {
+ ini, basePath string
+ }{
+ // packages storage inherits from storage if nothing configured
+ {`
[storage]
STORAGE_TYPE = minio
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadRepoArchiveFrom(cfg))
-
- assert.EqualValues(t, "minio", RepoArchive.Storage.Type)
- assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
-
- // we can also configure packages storage directly
- iniStr = `
+`, "repo-archive/"},
+ // we can also configure packages storage directly
+ {`
[storage.repo-archive]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadRepoArchiveFrom(cfg))
-
- assert.EqualValues(t, "minio", RepoArchive.Storage.Type)
- assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
-
- // or we can indicate the storage type in the packages section
- iniStr = `
+`, "repo-archive/"},
+ // or we can indicate the storage type in the packages section
+ {`
[repo-archive]
STORAGE_TYPE = my_minio
[storage.my_minio]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadRepoArchiveFrom(cfg))
-
- assert.EqualValues(t, "minio", RepoArchive.Storage.Type)
- assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
-
- // or we can indicate the storage type and minio base path in the packages section
- iniStr = `
+`, "repo-archive/"},
+ // or we can indicate the storage type and minio base path in the packages section
+ {`
[repo-archive]
STORAGE_TYPE = my_minio
MINIO_BASE_PATH = my_archive/
[storage.my_minio]
STORAGE_TYPE = minio
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
- assert.NoError(t, loadRepoArchiveFrom(cfg))
+`, "my_archive/"},
+ } {
+ cfg, err := NewConfigProviderFromData(tc.ini)
+ assert.NoError(t, err)
+ assert.NoError(t, loadRepoArchiveFrom(cfg))
- assert.EqualValues(t, "minio", RepoArchive.Storage.Type)
- assert.Equal(t, "my_archive/", RepoArchive.Storage.MinioConfig.BasePath)
+ assert.EqualValues(t, "minio", RepoArchive.Storage.Type)
+ assert.Equal(t, tc.basePath, RepoArchive.Storage.MinioConfig.BasePath)
+ }
}
func Test_RepoArchiveStorage(t *testing.T) {
- iniStr := `
+ for _, ini := range []string{
+ `
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[storage]
STORAGE_TYPE = minio
@@ -77,17 +62,8 @@ MINIO_LOCATION = homenet
MINIO_USE_SSL = true
MINIO_ACCESS_KEY_ID = correct_key
MINIO_SECRET_ACCESS_KEY = correct_key
-`
- cfg, err := NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
-
- assert.NoError(t, loadRepoArchiveFrom(cfg))
- storage := RepoArchive.Storage
-
- assert.EqualValues(t, "minio", storage.Type)
- assert.Equal(t, "gitea", storage.MinioConfig.Bucket)
-
- iniStr = `
+`,
+ `
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[storage.repo-archive]
STORAGE_TYPE = s3
@@ -99,13 +75,15 @@ MINIO_LOCATION = homenet
MINIO_USE_SSL = true
MINIO_ACCESS_KEY_ID = correct_key
MINIO_SECRET_ACCESS_KEY = correct_key
-`
- cfg, err = NewConfigProviderFromData(iniStr)
- assert.NoError(t, err)
+`,
+ } {
+ cfg, err := NewConfigProviderFromData(ini)
+ assert.NoError(t, err)
- assert.NoError(t, loadRepoArchiveFrom(cfg))
- storage = RepoArchive.Storage
+ assert.NoError(t, loadRepoArchiveFrom(cfg))
+ storage := RepoArchive.Storage
- assert.EqualValues(t, "minio", storage.Type)
- assert.Equal(t, "gitea", storage.MinioConfig.Bucket)
+ assert.EqualValues(t, "minio", storage.Type)
+ assert.Equal(t, "gitea", storage.MinioConfig.Bucket)
+ }
}
diff --git a/modules/setting/service_test.go b/modules/setting/service_test.go
index 1527d4f263b..5dc8a8b08cf 100644
--- a/modules/setting/service_test.go
+++ b/modules/setting/service_test.go
@@ -54,77 +54,57 @@ func TestLoadServiceVisibilityModes(t *testing.T) {
}
return ret
}
- testCases := map[string]func(){
+ testCases := map[string]struct {
+ defaultMode structs.VisibleType
+ allowedModes []structs.VisibilityString
+ }{
`
[service]
DEFAULT_USER_VISIBILITY = public
ALLOWED_USER_VISIBILITY_MODES = public,limited,private
-`: func() {
- assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
- assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
- },
+`: {structs.VisibleTypePublic, []structs.VisibilityString{"public", "limited", "private"}},
`
[service]
DEFAULT_USER_VISIBILITY = public
- `: func() {
- assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
- assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
- },
+ `: {structs.VisibleTypePublic, []structs.VisibilityString{"public", "limited", "private"}},
`
[service]
DEFAULT_USER_VISIBILITY = limited
- `: func() {
- assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode)
- assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
- },
+ `: {structs.VisibleTypeLimited, []structs.VisibilityString{"public", "limited", "private"}},
`
[service]
ALLOWED_USER_VISIBILITY_MODES = public,limited,private
-`: func() {
- assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
- assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
- },
+`: {structs.VisibleTypePublic, []structs.VisibilityString{"public", "limited", "private"}},
`
[service]
DEFAULT_USER_VISIBILITY = public
ALLOWED_USER_VISIBILITY_MODES = limited,private
-`: func() {
- assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode)
- assert.Equal(t, visibleTypeSlice("limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
- },
+`: {structs.VisibleTypeLimited, []structs.VisibilityString{"limited", "private"}},
`
[service]
DEFAULT_USER_VISIBILITY = my_type
ALLOWED_USER_VISIBILITY_MODES = limited,private
-`: func() {
- assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode)
- assert.Equal(t, visibleTypeSlice("limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
- },
+`: {structs.VisibleTypeLimited, []structs.VisibilityString{"limited", "private"}},
`
[service]
DEFAULT_USER_VISIBILITY = my_type
-`: func() {
- assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
- assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
- },
+`: {structs.VisibleTypePublic, []structs.VisibilityString{"public", "limited", "private"}},
`
[service]
DEFAULT_USER_VISIBILITY = public
ALLOWED_USER_VISIBILITY_MODES = public, limit, privated
-`: func() {
- assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode)
- assert.Equal(t, visibleTypeSlice("public"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
- },
+`: {structs.VisibleTypePublic, []structs.VisibilityString{"public"}},
}
- for tc, fn := range testCases {
+ for tc, expected := range testCases {
t.Run(tc, func(t *testing.T) {
Service.AllowedUserVisibilityModesSlice = []bool{true, true, true}
Service.DefaultUserVisibilityMode = structs.VisibleTypePublic
cfg, err := NewConfigProviderFromData(tc)
assert.NoError(t, err)
loadServiceFrom(cfg)
- fn()
+ assert.Equal(t, expected.defaultMode, Service.DefaultUserVisibilityMode)
+ assert.Equal(t, visibleTypeSlice(expected.allowedModes...), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice())
})
}
}
diff --git a/modules/setting/storage_test.go b/modules/setting/storage_test.go
index 6f5a54c41c1..92f7208a111 100644
--- a/modules/setting/storage_test.go
+++ b/modules/setting/storage_test.go
@@ -156,185 +156,85 @@ func testLocalStoragePath(t *testing.T, appDataPath, iniStr string, cases []test
}
func Test_getStorageInheritStorageTypeLocal(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+ for _, tc := range []struct {
+ name, ini, basePath, archivePath string
+ }{
+ {"Default", `
[storage]
STORAGE_TYPE = local
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
- {loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/repo-archive"},
- {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalPath(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/appdata", "/appdata/repo-archive"},
+ {"Path", `
[storage]
STORAGE_TYPE = local
PATH = /data/gitea
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
- {loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/repo-archive"},
- {loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalRelativePath(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/data/gitea", "/data/gitea/repo-archive"},
+ {"RelativePath", `
[storage]
STORAGE_TYPE = local
PATH = storages
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/appdata/storages/attachments"},
- {loadLFSFrom, &LFS.Storage, "/appdata/storages/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/storages/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/appdata/storages/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/storages/repo-archive"},
- {loadActionsFrom, &Actions.LogStorage, "/appdata/storages/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/appdata/storages/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/storages/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalPathOverride(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/appdata/storages", "/appdata/storages/repo-archive"},
+ {"PathOverride", `
[storage]
STORAGE_TYPE = local
PATH = /data/gitea
[repo-archive]
PATH = /data/gitea/the-archives-dir
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
- {loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/the-archives-dir"},
- {loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalPathOverrideEmpty(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/data/gitea", "/data/gitea/the-archives-dir"},
+ {"PathOverrideEmpty", `
[storage]
STORAGE_TYPE = local
PATH = /data/gitea
[repo-archive]
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
- {loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/repo-archive"},
- {loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalRelativePathOverride(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/data/gitea", "/data/gitea/repo-archive"},
+ {"RelativePathOverride", `
[storage]
STORAGE_TYPE = local
PATH = /data/gitea
[repo-archive]
PATH = the-archives-dir
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
- {loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/the-archives-dir"},
- {loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalPathOverride3(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/data/gitea", "/data/gitea/the-archives-dir"},
+ {"PathOverride3", `
[storage.repo-archive]
STORAGE_TYPE = local
PATH = /data/gitea/archives
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
- {loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/archives"},
- {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalPathOverride3_5(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/appdata", "/data/gitea/archives"},
+ {"PathOverride3_5", `
[storage.repo-archive]
STORAGE_TYPE = local
PATH = a-relative-path
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
- {loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/a-relative-path"},
- {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalPathOverride4(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/appdata", "/appdata/a-relative-path"},
+ {"PathOverride4", `
[storage.repo-archive]
STORAGE_TYPE = local
PATH = /data/gitea/archives
[repo-archive]
PATH = /tmp/gitea/archives
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
- {loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/tmp/gitea/archives"},
- {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
- })
-}
-
-func Test_getStorageInheritStorageTypeLocalPathOverride5(t *testing.T) {
- testLocalStoragePath(t, "/appdata", `
+`, "/appdata", "/tmp/gitea/archives"},
+ {"PathOverride5", `
[storage.repo-archive]
STORAGE_TYPE = local
PATH = /data/gitea/archives
[repo-archive]
-`, []testLocalStoragePathCase{
- {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
- {loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
- {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
- {loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
- {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/archives"},
- {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
- {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
- {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
- })
+`, "/appdata", "/data/gitea/archives"},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ testLocalStoragePath(t, "/appdata", tc.ini, []testLocalStoragePathCase{
+ {loadAttachmentFrom, &Attachment.Storage, tc.basePath + "/attachments"},
+ {loadLFSFrom, &LFS.Storage, tc.basePath + "/lfs"},
+ {loadActionsFrom, &Actions.ArtifactStorage, tc.basePath + "/actions_artifacts"},
+ {loadPackagesFrom, &Packages.Storage, tc.basePath + "/packages"},
+ {loadRepoArchiveFrom, &RepoArchive.Storage, tc.archivePath},
+ {loadActionsFrom, &Actions.LogStorage, tc.basePath + "/actions_log"},
+ {loadAvatarsFrom, &Avatar.Storage, tc.basePath + "/avatars"},
+ {loadRepoAvatarFrom, &RepoAvatar.Storage, tc.basePath + "/repo-avatars"},
+ })
+ })
+ }
}
func Test_getStorageInheritStorageTypeLocalPathOverride72(t *testing.T) {
diff --git a/modules/sitemap/sitemap_test.go b/modules/sitemap/sitemap_test.go
index 9ff97939014..75660a219f9 100644
--- a/modules/sitemap/sitemap_test.go
+++ b/modules/sitemap/sitemap_test.go
@@ -15,151 +15,90 @@ import (
func TestNewSitemap(t *testing.T) {
ts := time.Unix(1651322008, 0).UTC()
-
- tests := []struct {
- name string
- urls []URL
- want string
- wantErr string
+ for _, sitemap := range []struct {
+ name string
+ newSitemap func() *Sitemap
+ root, entry string
+ wantManyErr string
+ wantSizeErr string
}{
{
- name: "empty",
- urls: []URL{},
- want: xml.Header + `` +
- "" +
- "\n",
+ "NewSitemap", NewSitemap, "urlset", "url",
+ "sitemap contains 50001 URLs, but only 50000 are allowed",
+ "sitemap has 52428932 bytes, but only 52428800 are allowed",
},
{
- name: "regular",
- urls: []URL{
- {URL: "https://gitea.io/test1", LastMod: &ts},
- },
- want: xml.Header + `` +
- "https://gitea.io/test12022-04-30T12:33:28Z" +
- "\n",
+ "NewSitemapIndex", NewSitemapIndex, "sitemapindex", "sitemap",
+ "sitemap contains 50001 sub-sitemaps, but only 50000 are allowed",
+ "sitemap has 52428952 bytes, but only 52428800 are allowed",
},
- {
- name: "without lastmod",
- urls: []URL{
- {URL: "https://gitea.io/test1"},
- },
- want: xml.Header + `` +
- "https://gitea.io/test1" +
- "\n",
- },
- {
- name: "multiple",
- urls: []URL{
- {URL: "https://gitea.io/test1", LastMod: &ts},
- {URL: "https://gitea.io/test2", LastMod: nil},
- },
- want: xml.Header + `` +
- "https://gitea.io/test12022-04-30T12:33:28Z" +
- "https://gitea.io/test2" +
- "\n",
- },
- {
- name: "too many urls",
- urls: make([]URL, 50001),
- wantErr: "sitemap contains 50001 URLs, but only 50000 are allowed",
- },
- {
- name: "too big file",
- urls: []URL{
- {URL: strings.Repeat("b", 50*1024*1024+1)},
- },
- wantErr: "sitemap has 52428932 bytes, but only 52428800 are allowed",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := NewSitemap()
- for _, url := range tt.urls {
- s.Add(url)
+ } {
+ t.Run(sitemap.name, func(t *testing.T) {
+ entryStart, entryEnd := "<"+sitemap.entry+">", ""+sitemap.entry+">"
+ tests := []struct {
+ name string
+ urls []URL
+ want string
+ wantErr string
+ }{
+ {
+ name: "empty",
+ urls: []URL{},
+ },
+ {
+ name: "regular",
+ urls: []URL{
+ {URL: "https://gitea.io/test1", LastMod: &ts},
+ },
+ want: entryStart + "https://gitea.io/test12022-04-30T12:33:28Z" + entryEnd,
+ },
+ {
+ name: "without lastmod",
+ urls: []URL{
+ {URL: "https://gitea.io/test1"},
+ },
+ want: entryStart + "https://gitea.io/test1" + entryEnd,
+ },
+ {
+ name: "multiple",
+ urls: []URL{
+ {URL: "https://gitea.io/test1", LastMod: &ts},
+ {URL: "https://gitea.io/test2", LastMod: nil},
+ },
+ want: entryStart + "https://gitea.io/test12022-04-30T12:33:28Z" + entryEnd +
+ entryStart + "https://gitea.io/test2" + entryEnd,
+ },
+ {
+ name: "too many " + sitemap.entry + "s",
+ urls: make([]URL, 50001),
+ wantErr: sitemap.wantManyErr,
+ },
+ {
+ name: "too big file",
+ urls: []URL{
+ {URL: strings.Repeat("b", 50*1024*1024+1)},
+ },
+ wantErr: sitemap.wantSizeErr,
+ },
}
- buf := &bytes.Buffer{}
- _, err := s.WriteTo(buf)
- if tt.wantErr != "" {
- assert.EqualError(t, err, tt.wantErr)
- } else {
- assert.NoError(t, err)
- assert.Equalf(t, tt.want, buf.String(), "NewSitemap()")
- }
- })
- }
-}
-
-func TestNewSitemapIndex(t *testing.T) {
- ts := time.Unix(1651322008, 0).UTC()
-
- tests := []struct {
- name string
- urls []URL
- want string
- wantErr string
- }{
- {
- name: "empty",
- urls: []URL{},
- want: xml.Header + `` +
- "" +
- "\n",
- },
- {
- name: "regular",
- urls: []URL{
- {URL: "https://gitea.io/test1", LastMod: &ts},
- },
- want: xml.Header + `` +
- "https://gitea.io/test12022-04-30T12:33:28Z" +
- "\n",
- },
- {
- name: "without lastmod",
- urls: []URL{
- {URL: "https://gitea.io/test1"},
- },
- want: xml.Header + `` +
- "https://gitea.io/test1" +
- "\n",
- },
- {
- name: "multiple",
- urls: []URL{
- {URL: "https://gitea.io/test1", LastMod: &ts},
- {URL: "https://gitea.io/test2", LastMod: nil},
- },
- want: xml.Header + `` +
- "https://gitea.io/test12022-04-30T12:33:28Z" +
- "https://gitea.io/test2" +
- "\n",
- },
- {
- name: "too many sitemaps",
- urls: make([]URL, 50001),
- wantErr: "sitemap contains 50001 sub-sitemaps, but only 50000 are allowed",
- },
- {
- name: "too big file",
- urls: []URL{
- {URL: strings.Repeat("b", 50*1024*1024+1)},
- },
- wantErr: "sitemap has 52428952 bytes, but only 52428800 are allowed",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- s := NewSitemapIndex()
- for _, url := range tt.urls {
- s.Add(url)
- }
- buf := &bytes.Buffer{}
- _, err := s.WriteTo(buf)
- if tt.wantErr != "" {
- assert.EqualError(t, err, tt.wantErr)
- } else {
- assert.NoError(t, err)
- assert.Equalf(t, tt.want, buf.String(), "NewSitemapIndex()")
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ s := sitemap.newSitemap()
+ for _, url := range tt.urls {
+ s.Add(url)
+ }
+ buf := &bytes.Buffer{}
+ _, err := s.WriteTo(buf)
+ if tt.wantErr != "" {
+ assert.EqualError(t, err, tt.wantErr)
+ } else {
+ assert.NoError(t, err)
+ assert.Equalf(t,
+ xml.Header+"<"+sitemap.root+` xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`+tt.want+""+sitemap.root+">\n",
+ buf.String(), sitemap.name+"()",
+ )
+ }
+ })
}
})
}
diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go
index 0116e89026f..0c597078f9d 100644
--- a/modules/storage/minio_test.go
+++ b/modules/storage/minio_test.go
@@ -52,18 +52,21 @@ func TestMinioCredentials(t *testing.T) {
FakeEndpoint = "http://localhost"
)
- t.Run("Static Credentials", func(t *testing.T) {
- cfg := setting.MinioStorageConfig{
- AccessKeyID: ExpectedAccessKey,
- SecretAccessKey: ExpectedSecretAccessKey,
- IamEndpoint: FakeEndpoint,
- }
+ checkCredentials := func(t *testing.T, cfg setting.MinioStorageConfig, suffix string) {
creds := buildMinioCredentials(cfg)
v, err := creds.Get()
assert.NoError(t, err)
- assert.Equal(t, ExpectedAccessKey, v.AccessKeyID)
- assert.Equal(t, ExpectedSecretAccessKey, v.SecretAccessKey)
+ assert.Equal(t, ExpectedAccessKey+suffix, v.AccessKeyID)
+ assert.Equal(t, ExpectedSecretAccessKey+suffix, v.SecretAccessKey)
+ }
+
+ t.Run("Static Credentials", func(t *testing.T) {
+ checkCredentials(t, setting.MinioStorageConfig{
+ AccessKeyID: ExpectedAccessKey,
+ SecretAccessKey: ExpectedSecretAccessKey,
+ IamEndpoint: FakeEndpoint,
+ }, "")
})
t.Run("Chain", func(t *testing.T) {
@@ -75,24 +78,14 @@ func TestMinioCredentials(t *testing.T) {
t.Setenv("MINIO_ACCESS_KEY", ExpectedAccessKey+"Minio")
t.Setenv("MINIO_SECRET_KEY", ExpectedSecretAccessKey+"Minio")
- creds := buildMinioCredentials(cfg)
- v, err := creds.Get()
-
- assert.NoError(t, err)
- assert.Equal(t, ExpectedAccessKey+"Minio", v.AccessKeyID)
- assert.Equal(t, ExpectedSecretAccessKey+"Minio", v.SecretAccessKey)
+ checkCredentials(t, cfg, "Minio")
})
t.Run("EnvAWS", func(t *testing.T) {
t.Setenv("AWS_ACCESS_KEY", ExpectedAccessKey+"AWS")
t.Setenv("AWS_SECRET_KEY", ExpectedSecretAccessKey+"AWS")
- creds := buildMinioCredentials(cfg)
- v, err := creds.Get()
-
- assert.NoError(t, err)
- assert.Equal(t, ExpectedAccessKey+"AWS", v.AccessKeyID)
- assert.Equal(t, ExpectedSecretAccessKey+"AWS", v.SecretAccessKey)
+ checkCredentials(t, cfg, "AWS")
})
t.Run("FileMinio", func(t *testing.T) {
@@ -100,12 +93,7 @@ func TestMinioCredentials(t *testing.T) {
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/minio.json")
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")
- creds := buildMinioCredentials(cfg)
- v, err := creds.Get()
-
- assert.NoError(t, err)
- assert.Equal(t, ExpectedAccessKey+"MinioFile", v.AccessKeyID)
- assert.Equal(t, ExpectedSecretAccessKey+"MinioFile", v.SecretAccessKey)
+ checkCredentials(t, cfg, "MinioFile")
})
t.Run("FileAWS", func(t *testing.T) {
@@ -113,12 +101,7 @@ func TestMinioCredentials(t *testing.T) {
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/aws_credentials")
- creds := buildMinioCredentials(cfg)
- v, err := creds.Get()
-
- assert.NoError(t, err)
- assert.Equal(t, ExpectedAccessKey+"AWSFile", v.AccessKeyID)
- assert.Equal(t, ExpectedSecretAccessKey+"AWSFile", v.SecretAccessKey)
+ checkCredentials(t, cfg, "AWSFile")
})
t.Run("IAM", func(t *testing.T) {
@@ -139,14 +122,9 @@ func TestMinioCredentials(t *testing.T) {
defer server.Close()
// Use the provided EC2 Instance Metadata server
- creds := buildMinioCredentials(setting.MinioStorageConfig{
+ checkCredentials(t, setting.MinioStorageConfig{
IamEndpoint: server.URL,
- })
- v, err := creds.Get()
-
- assert.NoError(t, err)
- assert.Equal(t, ExpectedAccessKey+"IAM", v.AccessKeyID)
- assert.Equal(t, ExpectedSecretAccessKey+"IAM", v.SecretAccessKey)
+ }, "IAM")
})
})
}
diff --git a/modules/storage/storage_test.go b/modules/storage/storage_test.go
index 68665eb4b51..1a23050402c 100644
--- a/modules/storage/storage_test.go
+++ b/modules/storage/storage_test.go
@@ -15,37 +15,22 @@ import (
)
func TestObjectStoragePath(t *testing.T) {
- base := ""
- assert.Empty(t, buildObjectStorePath(base, "/"))
- assert.Empty(t, buildObjectStorePath(base, "."))
- assert.Equal(t, "a", buildObjectStorePath(base, "/a"))
- assert.Equal(t, "a/b", buildObjectStorePath(base, "/a/b/"))
- assert.Empty(t, buildObjectStorePathPrefix(base, ""))
- assert.Equal(t, "a/", buildObjectStorePathPrefix(base, "/a/"))
-
- base = "/"
- assert.Empty(t, buildObjectStorePath(base, "/"))
- assert.Empty(t, buildObjectStorePath(base, "."))
- assert.Equal(t, "a", buildObjectStorePath(base, "/a"))
- assert.Equal(t, "a/b", buildObjectStorePath(base, "/a/b/"))
- assert.Empty(t, buildObjectStorePathPrefix(base, ""))
- assert.Equal(t, "a/", buildObjectStorePathPrefix(base, "/a/"))
-
- base = "/base"
- assert.Equal(t, "base", buildObjectStorePath(base, "/"))
- assert.Equal(t, "base", buildObjectStorePath(base, "."))
- assert.Equal(t, "base/a", buildObjectStorePath(base, "/a"))
- assert.Equal(t, "base/a/b", buildObjectStorePath(base, "/a/b/"))
- assert.Equal(t, "base/", buildObjectStorePathPrefix(base, ""))
- assert.Equal(t, "base/a/", buildObjectStorePathPrefix(base, "/a/"))
-
- base = "/base/"
- assert.Equal(t, "base", buildObjectStorePath(base, "/"))
- assert.Equal(t, "base", buildObjectStorePath(base, "."))
- assert.Equal(t, "base/a", buildObjectStorePath(base, "/a"))
- assert.Equal(t, "base/a/b", buildObjectStorePath(base, "/a/b/"))
- assert.Equal(t, "base/", buildObjectStorePathPrefix(base, ""))
- assert.Equal(t, "base/a/", buildObjectStorePathPrefix(base, "/a/"))
+ for _, base := range []string{"", "/"} {
+ assert.Empty(t, buildObjectStorePath(base, "/"))
+ assert.Empty(t, buildObjectStorePath(base, "."))
+ assert.Equal(t, "a", buildObjectStorePath(base, "/a"))
+ assert.Equal(t, "a/b", buildObjectStorePath(base, "/a/b/"))
+ assert.Empty(t, buildObjectStorePathPrefix(base, ""))
+ assert.Equal(t, "a/", buildObjectStorePathPrefix(base, "/a/"))
+ }
+ for _, base := range []string{"/base", "/base/"} {
+ assert.Equal(t, "base", buildObjectStorePath(base, "/"))
+ assert.Equal(t, "base", buildObjectStorePath(base, "."))
+ assert.Equal(t, "base/a", buildObjectStorePath(base, "/a"))
+ assert.Equal(t, "base/a/b", buildObjectStorePath(base, "/a/b/"))
+ assert.Equal(t, "base/", buildObjectStorePathPrefix(base, ""))
+ assert.Equal(t, "base/a/", buildObjectStorePathPrefix(base, "/a/"))
+ }
}
func testStorageAccess(t *testing.T, objStore ObjectStorage) {
diff --git a/modules/structs/hook_test.go b/modules/structs/hook_test.go
index 269b3933975..2e4c4745a64 100644
--- a/modules/structs/hook_test.go
+++ b/modules/structs/hook_test.go
@@ -12,21 +12,33 @@ import (
"github.com/stretchr/testify/require"
)
-func TestPullRequestPayloadSynchronizeBeforeAfter(t *testing.T) {
- payload := &PullRequestPayload{
- Action: HookIssueSynchronized,
- Before: "1111111111111111111111111111111111111111",
- After: "2222222222222222222222222222222222222222",
- Index: 12,
- }
-
- data, err := json.Marshal(payload)
- require.NoError(t, err)
-
- assert.JSONEq(t, `{
+func TestPullRequestPayload(t *testing.T) {
+ for _, tt := range []struct {
+ payload PullRequestPayload
+ want string
+ }{
+ {
+ payload: PullRequestPayload{
+ Action: HookIssueSynchronized,
+ Before: "1111111111111111111111111111111111111111",
+ After: "2222222222222222222222222222222222222222",
+ Index: 12,
+ },
+ want: `
"action": "synchronized",
"before": "1111111111111111111111111111111111111111",
- "after": "2222222222222222222222222222222222222222",
+ "after": "2222222222222222222222222222222222222222",`,
+ },
+ {
+ payload: PullRequestPayload{Action: HookIssueOpened, Index: 12},
+ want: `
+ "action": "opened",`,
+ },
+ } {
+ t.Run(string(tt.payload.Action), func(t *testing.T) {
+ data, err := json.Marshal(&tt.payload)
+ require.NoError(t, err)
+ assert.JSONEq(t, `{`+tt.want+`
"number": 12,
"commit_id": "",
"pull_request": null,
@@ -35,25 +47,6 @@ func TestPullRequestPayloadSynchronizeBeforeAfter(t *testing.T) {
"review": null,
"sender": null
}`, string(data))
-}
-
-func TestPullRequestPayloadNonSynchronizeOmitsBeforeAfter(t *testing.T) {
- payload := &PullRequestPayload{
- Action: HookIssueOpened,
- Index: 12,
+ })
}
-
- data, err := json.Marshal(payload)
- require.NoError(t, err)
-
- assert.JSONEq(t, `{
- "action": "opened",
- "number": 12,
- "commit_id": "",
- "pull_request": null,
- "repository": null,
- "requested_reviewer": null,
- "review": null,
- "sender": null
- }`, string(data))
}
diff --git a/modules/structs/issue_test.go b/modules/structs/issue_test.go
index 19ac643d4fc..17af4833d4d 100644
--- a/modules/structs/issue_test.go
+++ b/modules/structs/issue_test.go
@@ -46,39 +46,22 @@ func TestIssueTemplateStringSlice_UnmarshalYAML(t *testing.T) {
tests := []struct {
name string
content string
- tmpl *IssueTemplate
- want *IssueTemplate
+ labels []string
wantErr string
}{
{
name: "array",
content: `labels: ["a", "b", "c"]`,
- tmpl: &IssueTemplate{
- Labels: []string{"should_be_overwrote"},
- },
- want: &IssueTemplate{
- Labels: []string{"a", "b", "c"},
- },
+ labels: []string{"a", "b", "c"},
},
{
name: "string",
content: `labels: "a,b,c"`,
- tmpl: &IssueTemplate{
- Labels: []string{"should_be_overwrote"},
- },
- want: &IssueTemplate{
- Labels: []string{"a", "b", "c"},
- },
+ labels: []string{"a", "b", "c"},
},
{
name: "empty",
content: `labels:`,
- tmpl: &IssueTemplate{
- Labels: []string{"should_be_overwrote"},
- },
- want: &IssueTemplate{
- Labels: nil,
- },
},
{
name: "error",
@@ -87,18 +70,21 @@ labels:
a: aa
b: bb
`,
- tmpl: &IssueTemplate{},
wantErr: "yaml: construct errors: line 3: cannot unmarshal !!map into IssueTemplateStringSlice",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- err := yaml.Unmarshal([]byte(tt.content), tt.tmpl)
+ tmpl := &IssueTemplate{}
+ if tt.wantErr == "" {
+ tmpl.Labels = []string{"should_be_overwrote"}
+ }
+ err := yaml.Unmarshal([]byte(tt.content), tmpl)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
- assert.Equal(t, tt.want, tt.tmpl)
+ assert.Equal(t, &IssueTemplate{Labels: tt.labels}, tmpl)
}
})
}
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index f04724bfe67..36be81e8786 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -15,47 +15,29 @@ import (
)
func TestSubjectBodySeparator(t *testing.T) {
- test := func(input, subject, body string) {
- loc := mailSubjectSplit.FindStringIndex(input)
+ for _, testCase := range []struct {
+ input, subject, body string
+ }{
+ {"Simple\n---------------\nCase", "Simple\n", "\nCase"},
+ {"Only\nBody", "", "Only\nBody"},
+ {"Minimal\n---\nseparator", "Minimal\n", "\nseparator"},
+ {"False --- separator", "", "False --- separator"},
+ {"False\n--- separator", "", "False\n--- separator"},
+ {"False ---\nseparator", "", "False ---\nseparator"},
+ {"With extra spaces\n----- \t \nBody", "With extra spaces\n", "\nBody"},
+ {"With leading spaces\n -------\nOnly body", "", "With leading spaces\n -------\nOnly body"},
+ {"Multiple\n---\n-------\n---\nSeparators", "Multiple\n", "\n-------\n---\nSeparators"},
+ {"Insufficient\n--\nSeparators", "", "Insufficient\n--\nSeparators"},
+ } {
+ loc := mailSubjectSplit.FindStringIndex(testCase.input)
if loc == nil {
- assert.Empty(t, subject, "no subject found, but one expected")
- assert.Equal(t, body, input)
+ assert.Empty(t, testCase.subject, "no subject found, but one expected")
+ assert.Equal(t, testCase.body, testCase.input)
} else {
- assert.Equal(t, subject, input[0:loc[0]])
- assert.Equal(t, body, input[loc[1]:])
+ assert.Equal(t, testCase.subject, testCase.input[0:loc[0]])
+ assert.Equal(t, testCase.body, testCase.input[loc[1]:])
}
}
-
- test("Simple\n---------------\nCase",
- "Simple\n",
- "\nCase")
- test("Only\nBody",
- "",
- "Only\nBody")
- test("Minimal\n---\nseparator",
- "Minimal\n",
- "\nseparator")
- test("False --- separator",
- "",
- "False --- separator")
- test("False\n--- separator",
- "",
- "False\n--- separator")
- test("False ---\nseparator",
- "",
- "False ---\nseparator")
- test("With extra spaces\n----- \t \nBody",
- "With extra spaces\n",
- "\nBody")
- test("With leading spaces\n -------\nOnly body",
- "",
- "With leading spaces\n -------\nOnly body")
- test("Multiple\n---\n-------\n---\nSeparators",
- "Multiple\n",
- "\n-------\n---\nSeparators")
- test("Insufficient\n--\nSeparators",
- "",
- "Insufficient\n--\nSeparators")
}
func TestSanitizeHTML(t *testing.T) {
@@ -148,33 +130,20 @@ func TestQueryBuild(t *testing.T) {
assert.Equal(t, "a=1&b=2&", string(QueryBuild("a=1&", "b", 2)))
})
- t.Run("replace", func(t *testing.T) {
- assert.Equal(t, "a=1&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", 1)))
- assert.Equal(t, "a=b&c=1&e=f", string(QueryBuild("a=b&c=d&e=f", "c", 1)))
- assert.Equal(t, "a=b&c=d&e=1", string(QueryBuild("a=b&c=d&e=f", "e", 1)))
- assert.Equal(t, "a=b&c=d&e=f&k=1", string(QueryBuild("a=b&c=d&e=f", "k", 1)))
- })
-
- t.Run("replace-&", func(t *testing.T) {
- assert.Equal(t, "&a=1&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", 1)))
- assert.Equal(t, "&a=b&c=1&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", 1)))
- assert.Equal(t, "&a=b&c=d&e=1", string(QueryBuild("&a=b&c=d&e=f", "e", 1)))
- assert.Equal(t, "&a=b&c=d&e=f&k=1", string(QueryBuild("&a=b&c=d&e=f", "k", 1)))
- })
-
- t.Run("delete", func(t *testing.T) {
- assert.Equal(t, "c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", "")))
- assert.Equal(t, "a=b&e=f", string(QueryBuild("a=b&c=d&e=f", "c", "")))
- assert.Equal(t, "a=b&c=d", string(QueryBuild("a=b&c=d&e=f", "e", "")))
- assert.Equal(t, "a=b&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "k", "")))
- })
-
- t.Run("delete-&", func(t *testing.T) {
- assert.Equal(t, "&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", "")))
- assert.Equal(t, "&a=b&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", "")))
- assert.Equal(t, "&a=b&c=d", string(QueryBuild("&a=b&c=d&e=f", "e", "")))
- assert.Equal(t, "&a=b&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "k", "")))
- })
+ for _, prefix := range []string{"", "&"} {
+ t.Run("replace"+prefix, func(t *testing.T) {
+ assert.Equal(t, prefix+"a=1&c=d&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "a", 1)))
+ assert.Equal(t, prefix+"a=b&c=1&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "c", 1)))
+ assert.Equal(t, prefix+"a=b&c=d&e=1", string(QueryBuild(prefix+"a=b&c=d&e=f", "e", 1)))
+ assert.Equal(t, prefix+"a=b&c=d&e=f&k=1", string(QueryBuild(prefix+"a=b&c=d&e=f", "k", 1)))
+ })
+ t.Run("delete"+prefix, func(t *testing.T) {
+ assert.Equal(t, prefix+"c=d&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "a", "")))
+ assert.Equal(t, prefix+"a=b&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "c", "")))
+ assert.Equal(t, prefix+"a=b&c=d", string(QueryBuild(prefix+"a=b&c=d&e=f", "e", "")))
+ assert.Equal(t, prefix+"a=b&c=d&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "k", "")))
+ })
+ }
}
const queryNonASCII = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" // all non-letter & non-number chars
diff --git a/modules/templates/util_date_test.go b/modules/templates/util_date_test.go
index 577d1bba5d5..4e4b448a3e9 100644
--- a/modules/templates/util_date_test.go
+++ b/modules/templates/util_date_test.go
@@ -39,21 +39,10 @@ func TestDateTime(t *testing.T) {
actual = du.FullTime(refTimeStamp)
assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual)
-}
-func TestTimeSince(t *testing.T) {
- testTz, _ := time.LoadLocation("America/New_York")
- defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
- defer test.MockVariableValue(&setting.IsProd, true)()
- defer test.MockVariableValue(&setting.IsInTesting, false)()
-
- du := NewDateUtils()
assert.EqualValues(t, "-", du.TimeSince(nil))
- refTimeStr := "2018-01-01T00:00:00Z"
- refTime, _ := time.Parse(time.RFC3339, refTimeStr)
-
- actual := du.TimeSince(refTime)
+ actual = du.TimeSince(refTime)
assert.EqualValues(t, `2018-01-01 00:00:00 +00:00`, actual)
actual = timeSinceTo(&refTime, time.Time{})
diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go
index 7a05284f7a8..b1901b5cf76 100644
--- a/modules/templates/util_render_test.go
+++ b/modules/templates/util_render_test.go
@@ -77,40 +77,18 @@ func TestRenderRepoComment(t *testing.T) {
}
t.Run("RenderCommitBody", func(t *testing.T) {
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
- type args struct {
- msg string
- }
tests := []struct {
name string
- args args
- want template.HTML
+ msg string
}{
- {
- name: "multiple lines",
- args: args{
- msg: "first line\nsecond line",
- },
- want: "second line",
- },
- {
- name: "multiple lines with leading newlines",
- args: args{
- msg: "\n\n\n\nfirst line\nsecond line",
- },
- want: "second line",
- },
- {
- name: "multiple lines with trailing newlines",
- args: args{
- msg: "first line\nsecond line\n\n\n",
- },
- want: "second line",
- },
+ {"multiple lines", "first line\nsecond line"},
+ {"multiple lines with leading newlines", "\n\n\n\nfirst line\nsecond line"},
+ {"multiple lines with trailing newlines", "first line\nsecond line\n\n\n"},
}
ut := newTestRenderUtils(t)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, mockRepo), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
+ assert.Equalf(t, template.HTML("second line"), ut.RenderCommitBody(tt.msg, mockRepo), "RenderCommitBody(%v, %v)", tt.msg, nil)
})
}
diff --git a/modules/util/diff_slice_test.go b/modules/util/diff_slice_test.go
index cd61fb571b1..1141cd5d5f8 100644
--- a/modules/util/diff_slice_test.go
+++ b/modules/util/diff_slice_test.go
@@ -10,65 +10,36 @@ import (
)
func TestDiffSliceBasic(t *testing.T) {
- // Typical integer cases
- t.Run("additions", func(t *testing.T) {
- added, removed := DiffSlice([]int{1, 2}, []int{1, 2, 3})
- assert.Equal(t, []int{3}, added)
- assert.Empty(t, removed)
- })
-
- t.Run("removals", func(t *testing.T) {
- added, removed := DiffSlice([]int{1, 2, 3}, []int{1, 2})
- assert.Empty(t, added)
- assert.Equal(t, []int{3}, removed)
- })
-
- t.Run("no changes", func(t *testing.T) {
- added, removed := DiffSlice([]int{1, 2}, []int{1, 2})
- assert.Empty(t, added)
- assert.Empty(t, removed)
- })
-
- t.Run("empty slices", func(t *testing.T) {
- added, removed := DiffSlice([]int{}, []int{})
- assert.Empty(t, added)
- assert.Empty(t, removed)
- })
-
- t.Run("overlapping elements", func(t *testing.T) {
- added, removed := DiffSlice([]int{1, 2, 4}, []int{2, 3, 4})
- assert.Equal(t, []int{3}, added)
- assert.Equal(t, []int{1}, removed)
- })
-}
-
-func TestDiffSliceOrderAndDuplicates(t *testing.T) {
- oldSlice := []int{1, 2, 2, 3}
- newSlice := []int{2, 4, 2, 5}
-
- added, removed := DiffSlice(oldSlice, newSlice)
- assert.Equal(t, []int{4, 5}, added)
- assert.Equal(t, []int{1, 3}, removed)
-}
-
-func TestDiffSliceDeduplicatesOutput(t *testing.T) {
- // Test case from issue: newSlice contains [4, 4, 5] and oldSlice is [1]
- // added should return [4, 5], not [4, 4, 5]
- t.Run("deduplicates added", func(t *testing.T) {
- added, removed := DiffSlice([]int{1}, []int{4, 4, 5})
- assert.Equal(t, []int{4, 5}, added)
- assert.Equal(t, []int{1}, removed)
- })
-
- t.Run("deduplicates removed", func(t *testing.T) {
- added, removed := DiffSlice([]int{1, 1, 2}, []int{3})
- assert.Equal(t, []int{3}, added)
- assert.Equal(t, []int{1, 2}, removed)
- })
-
- t.Run("deduplicates both", func(t *testing.T) {
- added, removed := DiffSlice([]int{1, 1, 2, 2}, []int{3, 3, 4, 4})
- assert.Equal(t, []int{3, 4}, added)
- assert.Equal(t, []int{1, 2}, removed)
- })
+ for _, testCase := range []struct {
+ name string
+ oldSlice, newSlice []int
+ expectedAdded, expectedRemoved []int
+ }{
+ // Typical integer cases
+ {"additions", []int{1, 2}, []int{1, 2, 3}, []int{3}, nil},
+ {"removals", []int{1, 2, 3}, []int{1, 2}, nil, []int{3}},
+ {"no changes", []int{1, 2}, []int{1, 2}, nil, nil},
+ {"empty slices", []int{}, []int{}, nil, nil},
+ {"overlapping elements", []int{1, 2, 4}, []int{2, 3, 4}, []int{3}, []int{1}},
+ {"order and duplicates", []int{1, 2, 2, 3}, []int{2, 4, 2, 5}, []int{4, 5}, []int{1, 3}},
+ // Test case from issue: newSlice contains [4, 4, 5] and oldSlice is [1]
+ // added should return [4, 5], not [4, 4, 5]
+ {"deduplicates added", []int{1}, []int{4, 4, 5}, []int{4, 5}, []int{1}},
+ {"deduplicates removed", []int{1, 1, 2}, []int{3}, []int{3}, []int{1, 2}},
+ {"deduplicates both", []int{1, 1, 2, 2}, []int{3, 3, 4, 4}, []int{3, 4}, []int{1, 2}},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ added, removed := DiffSlice(testCase.oldSlice, testCase.newSlice)
+ if testCase.expectedAdded == nil {
+ assert.Empty(t, added)
+ } else {
+ assert.Equal(t, testCase.expectedAdded, added)
+ }
+ if testCase.expectedRemoved == nil {
+ assert.Empty(t, removed)
+ } else {
+ assert.Equal(t, testCase.expectedRemoved, removed)
+ }
+ })
+ }
}
diff --git a/modules/util/filebuffer/file_backed_buffer_test.go b/modules/util/filebuffer/file_backed_buffer_test.go
index 3f13c6ac7bb..721f0fe993c 100644
--- a/modules/util/filebuffer/file_backed_buffer_test.go
+++ b/modules/util/filebuffer/file_backed_buffer_test.go
@@ -12,24 +12,16 @@ import (
)
func TestFileBackedBuffer(t *testing.T) {
- cases := []struct {
- MaxMemorySize int
- Data string
- }{
- {5, "test"},
- {5, "testtest"},
- }
-
- for _, c := range cases {
- buf := New(c.MaxMemorySize, t.TempDir())
- _, err := io.Copy(buf, strings.NewReader(c.Data))
+ for _, content := range []string{"test", "testtest"} {
+ buf := New(5, t.TempDir())
+ _, err := io.Copy(buf, strings.NewReader(content))
assert.NoError(t, err)
- assert.EqualValues(t, len(c.Data), buf.Size())
+ assert.EqualValues(t, len(content), buf.Size())
data, err := io.ReadAll(buf)
assert.NoError(t, err)
- assert.Equal(t, c.Data, string(data))
+ assert.Equal(t, content, string(data))
assert.NoError(t, buf.Close())
}
diff --git a/modules/util/io_test.go b/modules/util/io_test.go
index 275575463a6..a8cc5a31e38 100644
--- a/modules/util/io_test.go
+++ b/modules/util/io_test.go
@@ -26,28 +26,23 @@ func TestReadWithLimit(t *testing.T) {
bs := []byte("0123456789abcdef")
// normal test
- buf, err := readWithLimit(bytes.NewBuffer(bs), 5, 2)
- assert.NoError(t, err)
- assert.Equal(t, []byte("01"), buf)
-
- buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 5)
- assert.NoError(t, err)
- assert.Equal(t, []byte("01234"), buf)
-
- buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 6)
- assert.NoError(t, err)
- assert.Equal(t, []byte("012345"), buf)
-
- buf, err = readWithLimit(bytes.NewBuffer(bs), 5, len(bs))
- assert.NoError(t, err)
- assert.Equal(t, []byte("0123456789abcdef"), buf)
-
- buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 100)
- assert.NoError(t, err)
- assert.Equal(t, []byte("0123456789abcdef"), buf)
+ for _, testCase := range []struct {
+ limit int
+ expected string
+ }{
+ {2, "01"},
+ {5, "01234"},
+ {6, "012345"},
+ {len(bs), "0123456789abcdef"},
+ {100, "0123456789abcdef"},
+ } {
+ buf, err := readWithLimit(bytes.NewBuffer(bs), 5, testCase.limit)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte(testCase.expected), buf)
+ }
// test with error
- buf, err = readWithLimit(&readerWithError{bytes.NewBuffer(bs)}, 5, 10)
+ buf, err := readWithLimit(&readerWithError{bytes.NewBuffer(bs)}, 5, 10)
assert.NoError(t, err)
assert.Equal(t, []byte("0123456789"), buf)
diff --git a/modules/util/keypair_test.go b/modules/util/keypair_test.go
index f513bce815d..53c7abab3ef 100644
--- a/modules/util/keypair_test.go
+++ b/modules/util/keypair_test.go
@@ -25,11 +25,6 @@ func TestKeygen(t *testing.T) {
assert.Regexp(t, "^-----BEGIN RSA PRIVATE KEY-----.*", priv)
assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----.*", pub)
-}
-
-func TestSignUsingKeys(t *testing.T) {
- priv, pub, err := GenerateKeyPair(2048)
- assert.NoError(t, err)
privPem, _ := pem.Decode([]byte(priv))
if privPem == nil || privPem.Type != "RSA PRIVATE KEY" {
diff --git a/modules/util/path_test.go b/modules/util/path_test.go
index cb38644479d..8b024516263 100644
--- a/modules/util/path_test.go
+++ b/modules/util/path_test.go
@@ -159,34 +159,19 @@ func TestCleanPath(t *testing.T) {
{[]string{`a`}, `a`},
{[]string{`/a/`}, `a`},
{[]string{`../a/`, `../b`, `c/..`, `d`}, `a/b/d`},
- {[]string{`a\..\b`}, `a\..\b`},
{[]string{`a`, ``, `b`}, `a/b`},
{[]string{`a`, `..`, `b`}, `a/b`},
{[]string{`lfs`, `repo/..`, `user/../path`}, `lfs/path`},
}
for _, c := range cases {
assert.Equal(t, c.expected, PathJoinRel(c.elems...), "case: %v", c.elems)
- }
-
- cases = []struct {
- elems []string
- expected string
- }{
- {[]string{}, ``},
- {[]string{``}, ``},
- {[]string{`..`}, `.`},
- {[]string{`a`}, `a`},
- {[]string{`/a/`}, `a`},
- {[]string{`../a/`, `../b`, `c/..`, `d`}, `a/b/d`},
- {[]string{`a\..\b`}, `b`},
- {[]string{`a`, ``, `b`}, `a/b`},
- {[]string{`a`, `..`, `b`}, `a/b`},
- {[]string{`lfs`, `repo/..`, `user/../path`}, `lfs/path`},
- }
- for _, c := range cases {
assert.Equal(t, c.expected, PathJoinRelX(c.elems...), "case: %v", c.elems)
}
+ backslashPath := []string{`a\..\b`}
+ assert.Equal(t, `a\..\b`, PathJoinRel(backslashPath...), "case: %v", backslashPath)
+ assert.Equal(t, `b`, PathJoinRelX(backslashPath...), "case: %v", backslashPath)
+
// for POSIX only, but the result is similar on Windows, because the first element must be an absolute path
if consts.IsWindows {
cases = []struct {
diff --git a/modules/util/slice_test.go b/modules/util/slice_test.go
index a910f5edfe1..f01424ebedd 100644
--- a/modules/util/slice_test.go
+++ b/modules/util/slice_test.go
@@ -27,7 +27,6 @@ func TestSliceSortedEqual(t *testing.T) {
assert.True(t, SliceSortedEqual([]int{}, []int{}))
assert.True(t, SliceSortedEqual([]int(nil), nil))
assert.True(t, SliceSortedEqual([]int(nil), []int{}))
- assert.True(t, SliceSortedEqual([]int{}, []int{}))
assert.True(t, SliceSortedEqual([]string{"2", "0", "2", "3"}, []string{"2", "0", "2", "3"}))
assert.True(t, SliceSortedEqual([]float64{2, 0, 2, 3}, []float64{2, 0, 2, 3}))
@@ -47,7 +46,6 @@ func TestSliceRemoveAll(t *testing.T) {
assert.ElementsMatch(t, []int{2, 0, 2, 3}, SliceRemoveAll([]int{2, 0, 2, 3}, 4))
assert.Empty(t, SliceRemoveAll([]int{}, 0))
assert.ElementsMatch(t, []int(nil), SliceRemoveAll([]int(nil), 0))
- assert.Empty(t, SliceRemoveAll([]int{}, 0))
assert.ElementsMatch(t, []string{"2", "2", "3"}, SliceRemoveAll([]string{"2", "0", "2", "3"}, "0"))
assert.ElementsMatch(t, []float64{2, 2, 3}, SliceRemoveAll([]float64{2, 0, 2, 3}, 0))
diff --git a/modules/util/sync_test.go b/modules/util/sync_test.go
index 297328bd91f..51dc8d4717c 100644
--- a/modules/util/sync_test.go
+++ b/modules/util/sync_test.go
@@ -10,40 +10,31 @@ import (
)
func TestOnceValue(t *testing.T) {
- t.Run("RepeatCall", func(t *testing.T) {
- callCount := 0
- o := OnceValue[int]{Func: func() int {
- callCount++
- return 42
- }}
- assert.Equal(t, 42, o.Value())
- assert.Equal(t, 42, o.Value())
- assert.Equal(t, 1, callCount)
- o.Reset()
- assert.Equal(t, 42, o.Value())
- assert.Equal(t, 2, callCount)
- assert.Equal(t, 42, o.Value())
- assert.Equal(t, 2, callCount)
- })
-
- t.Run("Panic", func(t *testing.T) {
- callCount := 0
- doPanic := true
- o := OnceValue[int]{Func: func() int {
- callCount++
+ for _, name := range []string{"RepeatCall", "Panic"} {
+ t.Run(name, func(t *testing.T) {
+ callCount := 0
+ doPanic := name == "Panic"
+ o := OnceValue[int]{Func: func() int {
+ callCount++
+ if doPanic {
+ panic("some error")
+ }
+ return 42
+ }}
if doPanic {
- panic("some error")
+ assert.PanicsWithValue(t, "some error", func() { o.Value() })
+ assert.PanicsWithValue(t, "some error", func() { o.Value() })
+ } else {
+ assert.Equal(t, 42, o.Value())
+ assert.Equal(t, 42, o.Value())
}
- return 42
- }}
- assert.PanicsWithValue(t, "some error", func() { o.Value() })
- assert.PanicsWithValue(t, "some error", func() { o.Value() })
- assert.Equal(t, 1, callCount)
- doPanic = false
- o.Reset()
- assert.Equal(t, 42, o.Value())
- assert.Equal(t, 2, callCount)
- assert.Equal(t, 42, o.Value())
- assert.Equal(t, 2, callCount)
- })
+ assert.Equal(t, 1, callCount)
+ doPanic = false
+ o.Reset()
+ assert.Equal(t, 42, o.Value())
+ assert.Equal(t, 2, callCount)
+ assert.Equal(t, 42, o.Value())
+ assert.Equal(t, 2, callCount)
+ })
+ }
}
diff --git a/modules/util/util_test.go b/modules/util/util_test.go
index 50daaedaf8f..d87b4f2a3b9 100644
--- a/modules/util/util_test.go
+++ b/modules/util/util_test.go
@@ -40,26 +40,13 @@ func Test_NormalizeEOL(t *testing.T) {
"Ends without EOLtoo.",
}
- buildEOLData := func(data []string, eol string) []byte {
- return []byte(strings.Join(data, eol))
+ for _, data := range [][]string{data1, data2} {
+ unix := []byte(strings.Join(data, "\n"))
+ for _, input := range [][]byte{[]byte(strings.Join(data, "\r\n")), []byte(strings.Join(data, "\r")), unix} {
+ assert.Equal(t, unix, NormalizeEOL(input))
+ }
}
- dos := buildEOLData(data1, "\r\n")
- unix := buildEOLData(data1, "\n")
- mac := buildEOLData(data1, "\r")
-
- assert.Equal(t, unix, NormalizeEOL(dos))
- assert.Equal(t, unix, NormalizeEOL(mac))
- assert.Equal(t, unix, NormalizeEOL(unix))
-
- dos = buildEOLData(data2, "\r\n")
- unix = buildEOLData(data2, "\n")
- mac = buildEOLData(data2, "\r")
-
- assert.Equal(t, unix, NormalizeEOL(dos))
- assert.Equal(t, unix, NormalizeEOL(mac))
- assert.Equal(t, unix, NormalizeEOL(unix))
-
assert.Equal(t, []byte("one liner"), NormalizeEOL([]byte("one liner")))
assert.Equal(t, []byte("\n"), NormalizeEOL([]byte("\n")))
assert.Equal(t, []byte("\ntwo liner"), NormalizeEOL([]byte("\ntwo liner")))
@@ -103,17 +90,11 @@ func Test_RandomString(t *testing.T) {
}
func Test_RandomBytes(t *testing.T) {
- bytes1 := CryptoRandomBytes(32)
-
- bytes2 := CryptoRandomBytes(32)
-
- assert.NotEqual(t, bytes1, bytes2)
-
- bytes3 := CryptoRandomBytes(256)
-
- bytes4 := CryptoRandomBytes(256)
-
- assert.NotEqual(t, bytes3, bytes4)
+ for _, length := range []int64{32, 256} {
+ bytes1 := CryptoRandomBytes(length)
+ bytes2 := CryptoRandomBytes(length)
+ assert.NotEqual(t, bytes1, bytes2)
+ }
}
// Test case for any function which accepts and returns a single string.
diff --git a/modules/validation/refname_test.go b/modules/validation/refname_test.go
index 19e5cc5b915..3cf6c51aacb 100644
--- a/modules/validation/refname_test.go
+++ b/modules/validation/refname_test.go
@@ -8,249 +8,33 @@ import (
)
func Test_GitRefNameValidation(t *testing.T) {
- gitRefNameValidationTestCases := []validationTestCase{
- {
- description: "Reference name contains only characters",
- data: &TestForm{
- BranchName: "test",
- },
- },
- {
- description: "Reference name contains single slash",
- data: &TestForm{
- BranchName: "feature/test",
- },
- },
- {
- description: "Reference name has allowed special characters",
- data: &TestForm{
- BranchName: "debian/1%1.6.0-2",
- },
- },
- {
- description: "Reference name contains backslash",
- data: &TestForm{
- BranchName: "feature\\test",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name starts with dot",
- data: &TestForm{
- BranchName: ".test",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name ends with dot",
- data: &TestForm{
- BranchName: "test.",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name starts with slash",
- data: &TestForm{
- BranchName: "/test",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name ends with slash",
- data: &TestForm{
- BranchName: "test/",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name ends with .lock",
- data: &TestForm{
- BranchName: "test.lock",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name contains multiple consecutive dots",
- data: &TestForm{
- BranchName: "te..st",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name contains multiple consecutive slashes",
- data: &TestForm{
- BranchName: "te//st",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name is single @",
- data: &TestForm{
- BranchName: "@",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name has @{",
- data: &TestForm{
- BranchName: "branch@{",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name has unallowed special character ~",
- data: &TestForm{
- BranchName: "~debian/1%1.6.0-2",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name has unallowed special character *",
- data: &TestForm{
- BranchName: "*debian/1%1.6.0-2",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name has unallowed special character ?",
- data: &TestForm{
- BranchName: "?debian/1%1.6.0-2",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name has unallowed special character ^",
- data: &TestForm{
- BranchName: "^debian/1%1.6.0-2",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name has unallowed special character :",
- data: &TestForm{
- BranchName: "debian:jessie",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name has unallowed special character (whitespace)",
- data: &TestForm{
- BranchName: "debian jessie",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- {
- description: "Reference name has unallowed special character [",
- data: &TestForm{
- BranchName: "debian[jessie",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"BranchName"},
- Classification: ErrGitRefName,
- Message: "GitRefName",
- },
- },
- },
- }
-
- for _, testCase := range gitRefNameValidationTestCases {
+ expectedErrors := BindingErrors{{
+ FieldNames: []string{"BranchName"},
+ Classification: ErrGitRefName,
+ Message: "GitRefName",
+ }}
+ for _, testCase := range []validationTestCase{
+ {"Reference name contains only characters", &TestForm{BranchName: "test"}, nil},
+ {"Reference name contains single slash", &TestForm{BranchName: "feature/test"}, nil},
+ {"Reference name has allowed special characters", &TestForm{BranchName: "debian/1%1.6.0-2"}, nil},
+ {"Reference name contains backslash", &TestForm{BranchName: "feature\\test"}, expectedErrors},
+ {"Reference name starts with dot", &TestForm{BranchName: ".test"}, expectedErrors},
+ {"Reference name ends with dot", &TestForm{BranchName: "test."}, expectedErrors},
+ {"Reference name starts with slash", &TestForm{BranchName: "/test"}, expectedErrors},
+ {"Reference name ends with slash", &TestForm{BranchName: "test/"}, expectedErrors},
+ {"Reference name ends with .lock", &TestForm{BranchName: "test.lock"}, expectedErrors},
+ {"Reference name contains multiple consecutive dots", &TestForm{BranchName: "te..st"}, expectedErrors},
+ {"Reference name contains multiple consecutive slashes", &TestForm{BranchName: "te//st"}, expectedErrors},
+ {"Reference name is single @", &TestForm{BranchName: "@"}, expectedErrors},
+ {"Reference name has @{", &TestForm{BranchName: "branch@{"}, expectedErrors},
+ {"Reference name has unallowed special character ~", &TestForm{BranchName: "~debian/1%1.6.0-2"}, expectedErrors},
+ {"Reference name has unallowed special character *", &TestForm{BranchName: "*debian/1%1.6.0-2"}, expectedErrors},
+ {"Reference name has unallowed special character ?", &TestForm{BranchName: "?debian/1%1.6.0-2"}, expectedErrors},
+ {"Reference name has unallowed special character ^", &TestForm{BranchName: "^debian/1%1.6.0-2"}, expectedErrors},
+ {"Reference name has unallowed special character :", &TestForm{BranchName: "debian:jessie"}, expectedErrors},
+ {"Reference name has unallowed special character (whitespace)", &TestForm{BranchName: "debian jessie"}, expectedErrors},
+ {"Reference name has unallowed special character [", &TestForm{BranchName: "debian[jessie"}, expectedErrors},
+ } {
t.Run(testCase.description, func(t *testing.T) {
performValidationTest(t, testCase)
})
diff --git a/modules/validation/validurl_test.go b/modules/validation/validurl_test.go
index d2f0eba3994..6dd27015554 100644
--- a/modules/validation/validurl_test.go
+++ b/modules/validation/validurl_test.go
@@ -8,92 +8,22 @@ import (
)
func Test_ValidURLValidation(t *testing.T) {
- urlValidationTestCases := []validationTestCase{
- {
- description: "Empty URL",
- data: &TestForm{
- URL: "",
- },
- },
- {
- description: "URL without port",
- data: &TestForm{
- URL: "http://test.lan/",
- },
- },
- {
- description: "URL with port",
- data: &TestForm{
- URL: "http://test.lan:3000/",
- },
- },
- {
- description: "URL with IPv6 address without port",
- data: &TestForm{
- URL: "http://[::1]/",
- },
- },
- {
- description: "URL with IPv6 address with port",
- data: &TestForm{
- URL: "http://[::1]:3000/",
- },
- },
- {
- description: "Invalid URL",
- data: &TestForm{
- URL: "http//test.lan/",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"URL"},
- Classification: "UrlError",
- Message: "Url",
- },
- },
- },
- {
- description: "Invalid schema",
- data: &TestForm{
- URL: "ftp://test.lan/",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"URL"},
- Classification: "UrlError",
- Message: "Url",
- },
- },
- },
- {
- description: "Invalid port",
- data: &TestForm{
- URL: "http://test.lan:3x4/",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"URL"},
- Classification: "UrlError",
- Message: "Url",
- },
- },
- },
- {
- description: "Invalid port with IPv6 address",
- data: &TestForm{
- URL: "http://[::1]:3x4/",
- },
- expectedErrors: BindingErrors{
- BindingError{
- FieldNames: []string{"URL"},
- Classification: "UrlError",
- Message: "Url",
- },
- },
- },
- }
-
- for _, testCase := range urlValidationTestCases {
+ expectedErrors := BindingErrors{{
+ FieldNames: []string{"URL"},
+ Classification: "UrlError",
+ Message: "Url",
+ }}
+ for _, testCase := range []validationTestCase{
+ {"Empty URL", &TestForm{URL: ""}, nil},
+ {"URL without port", &TestForm{URL: "http://test.lan/"}, nil},
+ {"URL with port", &TestForm{URL: "http://test.lan:3000/"}, nil},
+ {"URL with IPv6 address without port", &TestForm{URL: "http://[::1]/"}, nil},
+ {"URL with IPv6 address with port", &TestForm{URL: "http://[::1]:3000/"}, nil},
+ {"Invalid URL", &TestForm{URL: "http//test.lan/"}, expectedErrors},
+ {"Invalid schema", &TestForm{URL: "ftp://test.lan/"}, expectedErrors},
+ {"Invalid port", &TestForm{URL: "http://test.lan:3x4/"}, expectedErrors},
+ {"Invalid port with IPv6 address", &TestForm{URL: "http://[::1]:3x4/"}, expectedErrors},
+ } {
t.Run(testCase.description, func(t *testing.T) {
performValidationTest(t, testCase)
})
diff --git a/modules/web/routemock_test.go b/modules/web/routemock_test.go
index 0db19f7827b..9f660882376 100644
--- a/modules/web/routemock_test.go
+++ b/modules/web/routemock_test.go
@@ -28,43 +28,31 @@ func TestRouteMock(t *testing.T) {
}
r.Get("/foo", middleware1, RouterMockPoint("mock-point"), middleware2, handler)
- // normal request
- recorder := httptest.NewRecorder()
- req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.Len(t, recorder.Header(), 3)
- assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1"))
- assert.Equal(t, "m2", recorder.Header().Get("X-Test-Middleware2"))
- assert.Equal(t, "h", recorder.Header().Get("X-Test-Handler"))
- RouteMockReset()
-
- // mock at "mock-point"
- RouteMock("mock-point", func(resp http.ResponseWriter, req *http.Request) {
- resp.Header().Set("X-Test-MockPoint", "a")
- resp.WriteHeader(http.StatusOK)
- })
- recorder = httptest.NewRecorder()
- req, err = http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.Len(t, recorder.Header(), 2)
- assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1"))
- assert.Equal(t, "a", recorder.Header().Get("X-Test-MockPoint"))
- RouteMockReset()
-
- // mock at MockAfterMiddlewares
- RouteMock(MockAfterMiddlewares, func(resp http.ResponseWriter, req *http.Request) {
- resp.Header().Set("X-Test-MockPoint", "b")
- resp.WriteHeader(http.StatusOK)
- })
- recorder = httptest.NewRecorder()
- req, err = http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.Len(t, recorder.Header(), 3)
- assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1"))
- assert.Equal(t, "m2", recorder.Header().Get("X-Test-Middleware2"))
- assert.Equal(t, "b", recorder.Header().Get("X-Test-MockPoint"))
- RouteMockReset()
+ for _, tc := range []struct {
+ mockPoint, mockValue string
+ headers [][2]string
+ }{
+ // normal request
+ {headers: [][2]string{{"X-Test-Middleware1", "m1"}, {"X-Test-Middleware2", "m2"}, {"X-Test-Handler", "h"}}},
+ // mock at "mock-point"
+ {mockPoint: "mock-point", mockValue: "a", headers: [][2]string{{"X-Test-Middleware1", "m1"}, {"X-Test-MockPoint", "a"}}},
+ // mock at MockAfterMiddlewares
+ {mockPoint: MockAfterMiddlewares, mockValue: "b", headers: [][2]string{{"X-Test-Middleware1", "m1"}, {"X-Test-Middleware2", "m2"}, {"X-Test-MockPoint", "b"}}},
+ } {
+ if tc.mockPoint != "" {
+ RouteMock(tc.mockPoint, func(resp http.ResponseWriter, req *http.Request) {
+ resp.Header().Set("X-Test-MockPoint", tc.mockValue)
+ resp.WriteHeader(http.StatusOK)
+ })
+ }
+ recorder := httptest.NewRecorder()
+ req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.Len(t, recorder.Header(), len(tc.headers))
+ for _, header := range tc.headers {
+ assert.Equal(t, header[1], recorder.Header().Get(header[0]))
+ }
+ RouteMockReset()
+ }
}
diff --git a/modules/web/router_test.go b/modules/web/router_test.go
index 2dceedbb276..c1453217232 100644
--- a/modules/web/router_test.go
+++ b/modules/web/router_test.go
@@ -208,30 +208,18 @@ func TestRouter(t *testing.T) {
method: "GET",
handlerMarks: []string{"not-found:/api/v1"},
})
- testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
- })
-
- testRoute(t, "POST /api/v1/repos/the-user/the-repo/branches", resultStruct{
- method: "POST",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
- })
-
- testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/master", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"},
- })
-
- testRoute(t, "PATCH /api/v1/repos/the-user/the-repo/branches/master", resultStruct{
- method: "PATCH",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"},
- })
-
- testRoute(t, "DELETE /api/v1/repos/the-user/the-repo/branches/master", resultStruct{
- method: "DELETE",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"},
- })
+ for _, method := range []string{"GET", "POST"} {
+ testRoute(t, method+" /api/v1/repos/the-user/the-repo/branches", resultStruct{
+ method: method,
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
+ })
+ }
+ for _, method := range []string{"GET", "PATCH", "DELETE"} {
+ testRoute(t, method+" /api/v1/repos/the-user/the-repo/branches/master", resultStruct{
+ method: method,
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"},
+ })
+ }
})
t.Run("MatchPath", func(t *testing.T) {
diff --git a/modules/zstd/zstd_test.go b/modules/zstd/zstd_test.go
index 4c3f059103f..f7da0af0f7b 100644
--- a/modules/zstd/zstd_test.go
+++ b/modules/zstd/zstd_test.go
@@ -21,47 +21,35 @@ func TestWriterReader(t *testing.T) {
result := bytes.NewBuffer(nil)
- t.Run("regular", func(t *testing.T) {
- result.Reset()
- writer, err := NewWriter(result)
- require.NoError(t, err)
+ for _, testCase := range []struct {
+ name string
+ writerOptions []WriterOption
+ readerOptions []ReaderOption
+ }{
+ {"regular", nil, nil},
+ {"with options", []WriterOption{WithEncoderLevel(SpeedBestCompression)}, []ReaderOption{WithDecoderLowmem(true)}},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ result.Reset()
+ writer, err := NewWriter(result, testCase.writerOptions...)
+ require.NoError(t, err)
- _, err = io.Copy(writer, bytes.NewReader(testData))
- require.NoError(t, err)
- require.NoError(t, writer.Close())
+ _, err = io.Copy(writer, bytes.NewReader(testData))
+ require.NoError(t, err)
+ require.NoError(t, writer.Close())
- t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100)
+ t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100)
- reader, err := NewReader(result)
- require.NoError(t, err)
+ reader, err := NewReader(result, testCase.readerOptions...)
+ require.NoError(t, err)
- data, err := io.ReadAll(reader)
- require.NoError(t, err)
- require.NoError(t, reader.Close())
+ data, err := io.ReadAll(reader)
+ require.NoError(t, err)
+ require.NoError(t, reader.Close())
- assert.Equal(t, testData, data)
- })
-
- t.Run("with options", func(t *testing.T) {
- result.Reset()
- writer, err := NewWriter(result, WithEncoderLevel(SpeedBestCompression))
- require.NoError(t, err)
-
- _, err = io.Copy(writer, bytes.NewReader(testData))
- require.NoError(t, err)
- require.NoError(t, writer.Close())
-
- t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100)
-
- reader, err := NewReader(result, WithDecoderLowmem(true))
- require.NoError(t, err)
-
- data, err := io.ReadAll(reader)
- require.NoError(t, err)
- require.NoError(t, reader.Close())
-
- assert.Equal(t, testData, data)
- })
+ assert.Equal(t, testData, data)
+ })
+ }
t.Run("rejects window above 128 MiB", func(t *testing.T) {
frameWith256MiBWindow := []byte{0x28, 0xb5, 0x2f, 0xfd, 0x00, 0x90, 0x01, 0x00, 0x00}
@@ -77,10 +65,8 @@ func TestSeekableWriterReader(t *testing.T) {
result := bytes.NewBuffer(nil)
- t.Run("regular", func(t *testing.T) {
+ write := func(t *testing.T, testData []byte, blockSize int) {
result.Reset()
- blockSize := 10000
-
writer, err := NewSeekableWriter(result, blockSize)
require.NoError(t, err)
@@ -89,6 +75,10 @@ func TestSeekableWriterReader(t *testing.T) {
require.NoError(t, writer.Close())
t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100)
+ }
+
+ t.Run("regular", func(t *testing.T) {
+ write(t, testData, 10000)
reader, err := NewSeekableReader(bytes.NewReader(result.Bytes()))
require.NoError(t, err)
@@ -101,17 +91,8 @@ func TestSeekableWriterReader(t *testing.T) {
})
t.Run("seek read", func(t *testing.T) {
- result.Reset()
blockSize := 10000
-
- writer, err := NewSeekableWriter(result, blockSize)
- require.NoError(t, err)
-
- _, err = io.Copy(writer, bytes.NewReader(testData))
- require.NoError(t, err)
- require.NoError(t, writer.Close())
-
- t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100)
+ write(t, testData, blockSize)
assertReader := &assertReadSeeker{r: bytes.NewReader(result.Bytes())}
@@ -141,18 +122,7 @@ func TestSeekableWriterReader(t *testing.T) {
t.Run("tidy data", func(t *testing.T) {
testData := prepareTestData(t, 1000) // data size is less than a block
-
- result.Reset()
- blockSize := 10000
-
- writer, err := NewSeekableWriter(result, blockSize)
- require.NoError(t, err)
-
- _, err = io.Copy(writer, bytes.NewReader(testData))
- require.NoError(t, err)
- require.NoError(t, writer.Close())
-
- t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100)
+ write(t, testData, 10000)
reader, err := NewSeekableReader(bytes.NewReader(result.Bytes()))
require.NoError(t, err)
@@ -165,17 +135,7 @@ func TestSeekableWriterReader(t *testing.T) {
})
t.Run("tidy block", func(t *testing.T) {
- result.Reset()
- blockSize := 100
-
- writer, err := NewSeekableWriter(result, blockSize)
- require.NoError(t, err)
-
- _, err = io.Copy(writer, bytes.NewReader(testData))
- require.NoError(t, err)
- require.NoError(t, writer.Close())
-
- t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100)
+ write(t, testData, 100)
// A too small block size will cause a bad compression rate,
// even the compressed data is larger than the original data.
assert.Greater(t, result.Len(), len(testData))
@@ -191,17 +151,7 @@ func TestSeekableWriterReader(t *testing.T) {
})
t.Run("compatible reader", func(t *testing.T) {
- result.Reset()
- blockSize := 10000
-
- writer, err := NewSeekableWriter(result, blockSize)
- require.NoError(t, err)
-
- _, err = io.Copy(writer, bytes.NewReader(testData))
- require.NoError(t, err)
- require.NoError(t, writer.Close())
-
- t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100)
+ write(t, testData, 10000)
// It should be able to read the data with a regular reader.
reader, err := NewReader(bytes.NewReader(result.Bytes()))
diff --git a/routers/api/packages/generic/generic_test.go b/routers/api/packages/generic/generic_test.go
index 1acaafe576a..c8ccb55fee7 100644
--- a/routers/api/packages/generic/generic_test.go
+++ b/routers/api/packages/generic/generic_test.go
@@ -9,57 +9,32 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestValidatePackageName(t *testing.T) {
- bad := []string{
- "",
- ".",
- "..",
- "-",
- "a?b",
- "a b",
- "a/b",
- }
- for _, name := range bad {
- assert.False(t, isValidPackageName(name), "bad=%q", name)
- }
-
- good := []string{
- "a",
- "1",
- "a-",
- "a_b",
- "c.d+",
- }
- for _, name := range good {
- assert.True(t, isValidPackageName(name), "good=%q", name)
- }
-}
-
-func TestValidateFileName(t *testing.T) {
- bad := []string{
- "",
- ".",
- "..",
- "a?b",
- "a/b",
- " a",
- "a ",
- }
- for _, name := range bad {
- assert.False(t, isValidFileName(name), "bad=%q", name)
- }
-
- good := []string{
- "-",
- "a",
- "1",
- "a-",
- "a_b",
- "a b",
- "c.d+",
- `-_+=:;.()[]{}~!@#$%^& aA1`,
- }
- for _, name := range good {
- assert.True(t, isValidFileName(name), "good=%q", name)
+func TestValidateNames(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ validate func(string) bool
+ bad, good []string
+ }{
+ {
+ name: "package",
+ validate: isValidPackageName,
+ bad: []string{"", ".", "..", "-", "a?b", "a b", "a/b"},
+ good: []string{"a", "1", "a-", "a_b", "c.d+"},
+ },
+ {
+ name: "file",
+ validate: isValidFileName,
+ bad: []string{"", ".", "..", "a?b", "a/b", " a", "a "},
+ good: []string{"-", "a", "1", "a-", "a_b", "a b", "c.d+", `-_+=:;.()[]{}~!@#$%^& aA1`},
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ for _, name := range test.bad {
+ assert.False(t, test.validate(name), "bad=%q", name)
+ }
+ for _, name := range test.good {
+ assert.True(t, test.validate(name), "good=%q", name)
+ }
+ })
}
}
diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go
index d8b83ea330f..c70573a5596 100644
--- a/routers/api/v1/misc/markup_test.go
+++ b/routers/api/v1/misc/markup_test.go
@@ -143,17 +143,11 @@ Here are some links to the most important topics. You can find the full list of
}
input := "[Link](test.md)\n"
- testRenderMarkdown(t, "gfm", false, input, `Link
+ response := `
Link

-`, http.StatusOK)
-
- testRenderMarkdown(t, "gfm", false, input, `Link
-
-`, http.StatusOK)
-
- testRenderMarkup(t, "gfm", false, "", input, `Link
-
-`, http.StatusOK)
+`
+ testRenderMarkdown(t, "gfm", false, input, response, http.StatusOK)
+ testRenderMarkup(t, "gfm", false, "", input, response, http.StatusOK)
testRenderMarkup(t, "file", false, "path/new-file.md", input, `Link

diff --git a/routers/api/v1/utils/hook_test.go b/routers/api/v1/utils/hook_test.go
index 9e7f3af9814..0dc54e461bf 100644
--- a/routers/api/v1/utils/hook_test.go
+++ b/routers/api/v1/utils/hook_test.go
@@ -17,66 +17,29 @@ import (
func TestTestHookValidation(t *testing.T) {
unittest.PrepareTestEnv(t)
- t.Run("Test Validation", func(t *testing.T) {
- ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
+ cases := []struct {
+ name string
+ hookType string
+ config map[string]string
+ status int
+ }{
+ {"Test Validation", "gitea", map[string]string{"content_type": "json", "url": "https://example.com/webhook"}, 0}, // not written yet
+ {"Test Validation with invalid URL", "gitea", map[string]string{"content_type": "json", "url": "example.com/webhook"}, http.StatusUnprocessableEntity},
+ {"Test Validation with invalid webhook type", "unknown", map[string]string{"content_type": "json", "url": "example.com/webhook"}, http.StatusUnprocessableEntity},
+ {"Test Validation with empty content type", "unknown", map[string]string{"url": "https://example.com/webhook"}, http.StatusUnprocessableEntity},
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
- checkCreateHookOption(ctx, &structs.CreateHookOption{
- Type: "gitea",
- Config: map[string]string{
- "content_type": "json",
- "url": "https://example.com/webhook",
- },
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: tc.hookType,
+ Config: tc.config,
+ })
+ assert.Equal(t, tc.status, ctx.Resp.WrittenStatus())
})
- assert.Equal(t, 0, ctx.Resp.WrittenStatus()) // not written yet
- })
-
- t.Run("Test Validation with invalid URL", func(t *testing.T) {
- ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
-
- checkCreateHookOption(ctx, &structs.CreateHookOption{
- Type: "gitea",
- Config: map[string]string{
- "content_type": "json",
- "url": "example.com/webhook",
- },
- })
- assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
- })
-
- t.Run("Test Validation with invalid webhook type", func(t *testing.T) {
- ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
-
- checkCreateHookOption(ctx, &structs.CreateHookOption{
- Type: "unknown",
- Config: map[string]string{
- "content_type": "json",
- "url": "example.com/webhook",
- },
- })
- assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
- })
-
- t.Run("Test Validation with empty content type", func(t *testing.T) {
- ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
-
- checkCreateHookOption(ctx, &structs.CreateHookOption{
- Type: "unknown",
- Config: map[string]string{
- "url": "https://example.com/webhook",
- },
- })
- assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
- })
+ }
}
diff --git a/routers/web/admin/users_test.go b/routers/web/admin/users_test.go
index 9c88f34d724..e6fb885ae84 100644
--- a/routers/web/admin/users_test.go
+++ b/routers/web/admin/users_test.go
@@ -17,183 +17,55 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestNewUserPost_MustChangePassword(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- ctx, _ := contexttest.MockContext(t, "admin/users/new")
+func TestNewUserPost(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ email string
+ mustChangePassword bool
+ visibility api.VisibleType
+ }{
+ {name: "MustChangePassword", email: "gitea@gitea.io", mustChangePassword: true},
+ {name: "MustChangePasswordFalseAndDefaultVisibility", email: "gitea@gitea.io"},
+ {name: "InvalidEmail", email: "gitea@gitea.io\r\n"},
+ {name: "VisibilityPrivate", email: "gitea@gitea.io", visibility: api.VisibleTypePrivate},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ ctx, _ := contexttest.MockContext(t, "admin/users/new")
+ ctx.Doer = unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true, ID: 2})
- u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
- IsAdmin: true,
- ID: 2,
- })
+ web.SetForm(ctx, &forms.AdminCreateUserForm{
+ LoginType: "local",
+ LoginName: "local",
+ UserName: "gitea",
+ Email: tc.email,
+ Password: "abc123ABC!=$",
+ SendNotify: false,
+ MustChangePassword: tc.mustChangePassword,
+ Visibility: tc.visibility,
+ })
+ NewUserPost(ctx)
- ctx.Doer = u
-
- username := "gitea"
- email := "gitea@gitea.io"
-
- form := forms.AdminCreateUserForm{
- LoginType: "local",
- LoginName: "local",
- UserName: username,
- Email: email,
- Password: "abc123ABC!=$",
- SendNotify: false,
- MustChangePassword: true,
+ if tc.name == "InvalidEmail" {
+ assert.NotEmpty(t, ctx.Flash.ErrorMsg)
+ return
+ }
+ assert.NotEmpty(t, ctx.Flash.SuccessMsg)
+ user, err := user_model.GetUserByName(ctx, "gitea")
+ assert.NoError(t, err)
+ assert.Equal(t, "gitea", user.Name)
+ assert.Equal(t, tc.email, user.Email)
+ switch tc.name {
+ case "MustChangePassword":
+ assert.True(t, user.MustChangePassword)
+ case "MustChangePasswordFalseAndDefaultVisibility":
+ assert.False(t, user.MustChangePassword)
+ // As default user visibility
+ assert.Equal(t, setting.Service.DefaultUserVisibilityMode, user.Visibility)
+ case "VisibilityPrivate":
+ // As default user visibility
+ assert.True(t, user.Visibility.IsPrivate())
+ }
+ })
}
-
- web.SetForm(ctx, &form)
- NewUserPost(ctx)
-
- assert.NotEmpty(t, ctx.Flash.SuccessMsg)
-
- u, err := user_model.GetUserByName(ctx, username)
-
- assert.NoError(t, err)
- assert.Equal(t, username, u.Name)
- assert.Equal(t, email, u.Email)
- assert.True(t, u.MustChangePassword)
-}
-
-func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- ctx, _ := contexttest.MockContext(t, "admin/users/new")
-
- u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
- IsAdmin: true,
- ID: 2,
- })
-
- ctx.Doer = u
-
- username := "gitea"
- email := "gitea@gitea.io"
-
- form := forms.AdminCreateUserForm{
- LoginType: "local",
- LoginName: "local",
- UserName: username,
- Email: email,
- Password: "abc123ABC!=$",
- SendNotify: false,
- MustChangePassword: false,
- }
-
- web.SetForm(ctx, &form)
- NewUserPost(ctx)
-
- assert.NotEmpty(t, ctx.Flash.SuccessMsg)
-
- u, err := user_model.GetUserByName(ctx, username)
-
- assert.NoError(t, err)
- assert.Equal(t, username, u.Name)
- assert.Equal(t, email, u.Email)
- assert.False(t, u.MustChangePassword)
-}
-
-func TestNewUserPost_InvalidEmail(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- ctx, _ := contexttest.MockContext(t, "admin/users/new")
-
- u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
- IsAdmin: true,
- ID: 2,
- })
-
- ctx.Doer = u
-
- username := "gitea"
- email := "gitea@gitea.io\r\n"
-
- form := forms.AdminCreateUserForm{
- LoginType: "local",
- LoginName: "local",
- UserName: username,
- Email: email,
- Password: "abc123ABC!=$",
- SendNotify: false,
- MustChangePassword: false,
- }
-
- web.SetForm(ctx, &form)
- NewUserPost(ctx)
-
- assert.NotEmpty(t, ctx.Flash.ErrorMsg)
-}
-
-func TestNewUserPost_VisibilityDefaultPublic(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- ctx, _ := contexttest.MockContext(t, "admin/users/new")
-
- u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
- IsAdmin: true,
- ID: 2,
- })
-
- ctx.Doer = u
-
- username := "gitea"
- email := "gitea@gitea.io"
-
- form := forms.AdminCreateUserForm{
- LoginType: "local",
- LoginName: "local",
- UserName: username,
- Email: email,
- Password: "abc123ABC!=$",
- SendNotify: false,
- MustChangePassword: false,
- }
-
- web.SetForm(ctx, &form)
- NewUserPost(ctx)
-
- assert.NotEmpty(t, ctx.Flash.SuccessMsg)
-
- u, err := user_model.GetUserByName(ctx, username)
-
- assert.NoError(t, err)
- assert.Equal(t, username, u.Name)
- assert.Equal(t, email, u.Email)
- // As default user visibility
- assert.Equal(t, setting.Service.DefaultUserVisibilityMode, u.Visibility)
-}
-
-func TestNewUserPost_VisibilityPrivate(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- ctx, _ := contexttest.MockContext(t, "admin/users/new")
-
- u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
- IsAdmin: true,
- ID: 2,
- })
-
- ctx.Doer = u
-
- username := "gitea"
- email := "gitea@gitea.io"
-
- form := forms.AdminCreateUserForm{
- LoginType: "local",
- LoginName: "local",
- UserName: username,
- Email: email,
- Password: "abc123ABC!=$",
- SendNotify: false,
- MustChangePassword: false,
- Visibility: api.VisibleTypePrivate,
- }
-
- web.SetForm(ctx, &form)
- NewUserPost(ctx)
-
- assert.NotEmpty(t, ctx.Flash.SuccessMsg)
-
- u, err := user_model.GetUserByName(ctx, username)
-
- assert.NoError(t, err)
- assert.Equal(t, username, u.Name)
- assert.Equal(t, email, u.Email)
- // As default user visibility
- assert.True(t, u.Visibility.IsPrivate())
}
diff --git a/routers/web/repo/actions/view_test.go b/routers/web/repo/actions/view_test.go
index af8ccfe3fdc..c5001fa6b60 100644
--- a/routers/web/repo/actions/view_test.go
+++ b/routers/web/repo/actions/view_test.go
@@ -77,73 +77,56 @@ func TestViewSummaryBranchFromRun(t *testing.T) {
}
func TestConvertToViewModel(t *testing.T) {
- task := &actions_model.ActionTask{
- Status: actions_model.StatusSuccess,
- Steps: []*actions_model.ActionTaskStep{
- {Name: "Run step-name", Index: 0, Status: actions_model.StatusSuccess, LogLength: 1, Started: timeutil.TimeStamp(1), Stopped: timeutil.TimeStamp(5)},
+ for _, c := range []struct {
+ name string
+ task actions_model.ActionTask
+ expectedViewJobs []*ViewJobStep
+ }{
+ {
+ name: "success",
+ task: actions_model.ActionTask{
+ Status: actions_model.StatusSuccess,
+ Steps: []*actions_model.ActionTaskStep{
+ {Name: "Run step-name", Index: 0, Status: actions_model.StatusSuccess, LogLength: 1, Started: timeutil.TimeStamp(1), Stopped: timeutil.TimeStamp(5)},
+ },
+ Stopped: timeutil.TimeStamp(20),
+ },
+ expectedViewJobs: []*ViewJobStep{
+ {Summary: "Set up job", Duration: "0s", Status: "success"},
+ {Summary: "Run step-name", Duration: "4s", Status: "success"},
+ {Summary: "Complete job", Duration: "15s", Status: "success"},
+ },
},
- Stopped: timeutil.TimeStamp(20),
+ {
+ name: "cancelling task does not render running steps",
+ task: actions_model.ActionTask{
+ Status: actions_model.StatusCancelling,
+ Steps: []*actions_model.ActionTaskStep{
+ {Name: "Run step-name", Index: 0, Status: actions_model.StatusRunning, LogLength: 1},
+ },
+ },
+ expectedViewJobs: []*ViewJobStep{
+ {Summary: "Set up job", Duration: "0s", Status: "success"},
+ {Summary: "Run step-name", Duration: "0s", Status: "cancelling"},
+ {Summary: "Complete job", Duration: "0s", Status: "pending"},
+ },
+ },
+ } {
+ t.Run(c.name, func(t *testing.T) {
+ viewJobSteps, _, err := convertToViewModel(t.Context(), translation.MockLocale{}, nil, &c.task)
+ require.NoError(t, err)
+
+ if c.task.Status == actions_model.StatusSuccess {
+ t.Run("out of range cursor step", func(t *testing.T) {
+ cursors := []LogCursor{{Step: -1, Expanded: true}, {Step: 3, Expanded: true}}
+ _, logs, err := convertToViewModel(t.Context(), translation.MockLocale{}, cursors, &c.task)
+ require.NoError(t, err)
+ assert.Empty(t, logs)
+ })
+ }
+ assert.Equal(t, c.expectedViewJobs, viewJobSteps)
+ })
}
-
- viewJobSteps, _, err := convertToViewModel(t.Context(), translation.MockLocale{}, nil, task)
- require.NoError(t, err)
-
- t.Run("out of range cursor step", func(t *testing.T) {
- cursors := []LogCursor{{Step: -1, Expanded: true}, {Step: 3, Expanded: true}}
- _, logs, err := convertToViewModel(t.Context(), translation.MockLocale{}, cursors, task)
- require.NoError(t, err)
- assert.Empty(t, logs)
- })
-
- expectedViewJobs := []*ViewJobStep{
- {
- Summary: "Set up job",
- Duration: "0s",
- Status: "success",
- },
- {
- Summary: "Run step-name",
- Duration: "4s",
- Status: "success",
- },
- {
- Summary: "Complete job",
- Duration: "15s",
- Status: "success",
- },
- }
- assert.Equal(t, expectedViewJobs, viewJobSteps)
-}
-
-func TestConvertToViewModelCancellingTaskDoesNotRenderRunningSteps(t *testing.T) {
- task := &actions_model.ActionTask{
- Status: actions_model.StatusCancelling,
- Steps: []*actions_model.ActionTaskStep{
- {Name: "Run step-name", Index: 0, Status: actions_model.StatusRunning, LogLength: 1},
- },
- }
-
- viewJobSteps, _, err := convertToViewModel(t.Context(), translation.MockLocale{}, nil, task)
- require.NoError(t, err)
-
- expectedViewJobs := []*ViewJobStep{
- {
- Summary: "Set up job",
- Duration: "0s",
- Status: "success",
- },
- {
- Summary: "Run step-name",
- Duration: "0s",
- Status: "cancelling",
- },
- {
- Summary: "Complete job",
- Duration: "0s",
- Status: "pending",
- },
- }
- assert.Equal(t, expectedViewJobs, viewJobSteps)
}
func TestPendingNeeds(t *testing.T) {
diff --git a/routers/web/repo/issue_test.go b/routers/web/repo/issue_test.go
index 05a23fdbfc3..b59ff137a32 100644
--- a/routers/web/repo/issue_test.go
+++ b/routers/web/repo/issue_test.go
@@ -8,6 +8,7 @@ import (
issues_model "gitea.dev/models/issues"
"gitea.dev/modules/references"
+ "gitea.dev/modules/timeutil"
"github.com/stretchr/testify/assert"
)
@@ -36,6 +37,20 @@ func TestCombineXRefComments(t *testing.T) {
}
func TestCombineLabelComments(t *testing.T) {
+ labelComment := func(posterID int64, content, labelName string, createdUnix timeutil.TimeStamp, addedLabels, removedLabels []*issues_model.Label) *issues_model.Comment {
+ return &issues_model.Comment{
+ Type: issues_model.CommentTypeLabel,
+ PosterID: posterID,
+ Content: content,
+ Label: &issues_model.Label{Name: labelName},
+ CreatedUnix: createdUnix,
+ AddedLabels: addedLabels,
+ RemovedLabels: removedLabels,
+ }
+ }
+ comment := func(posterID int64, content string) *issues_model.Comment {
+ return &issues_model.Comment{Type: issues_model.CommentTypeComment, PosterID: posterID, Content: content, CreatedUnix: 0}
+ }
kases := []struct {
name string
beforeCombined []*issues_model.Comment
@@ -44,178 +59,39 @@ func TestCombineLabelComments(t *testing.T) {
{
name: "kase 1",
beforeCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeComment,
- PosterID: 1,
- Content: "test",
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, nil, nil),
+ labelComment(1, "", "kind/bug", 0, nil, nil),
+ comment(1, "test"),
},
afterCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- CreatedUnix: 0,
- AddedLabels: []*issues_model.Label{},
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- },
- {
- Type: issues_model.CommentTypeComment,
- PosterID: 1,
- Content: "test",
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{}, nil),
+ comment(1, "test"),
},
},
{
name: "kase 2",
beforeCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 70,
- },
- {
- Type: issues_model.CommentTypeComment,
- PosterID: 1,
- Content: "test",
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, nil, nil),
+ labelComment(1, "", "kind/bug", 70, nil, nil),
+ comment(1, "test"),
},
afterCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- CreatedUnix: 0,
- AddedLabels: []*issues_model.Label{
- {
- Name: "kind/bug",
- },
- },
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "",
- CreatedUnix: 70,
- RemovedLabels: []*issues_model.Label{
- {
- Name: "kind/bug",
- },
- },
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- },
- {
- Type: issues_model.CommentTypeComment,
- PosterID: 1,
- Content: "test",
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{{Name: "kind/bug"}}, nil),
+ labelComment(1, "", "kind/bug", 70, nil, []*issues_model.Label{{Name: "kind/bug"}}),
+ comment(1, "test"),
},
},
{
name: "kase 3",
beforeCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 2,
- Content: "",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeComment,
- PosterID: 1,
- Content: "test",
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, nil, nil),
+ labelComment(2, "", "kind/bug", 0, nil, nil),
+ comment(1, "test"),
},
afterCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- CreatedUnix: 0,
- AddedLabels: []*issues_model.Label{
- {
- Name: "kind/bug",
- },
- },
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 2,
- Content: "",
- CreatedUnix: 0,
- RemovedLabels: []*issues_model.Label{
- {
- Name: "kind/bug",
- },
- },
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- },
- {
- Type: issues_model.CommentTypeComment,
- PosterID: 1,
- Content: "test",
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{{Name: "kind/bug"}}, nil),
+ labelComment(2, "", "kind/bug", 0, nil, []*issues_model.Label{{Name: "kind/bug"}}),
+ comment(1, "test"),
},
},
{
@@ -234,168 +110,36 @@ func TestCombineLabelComments(t *testing.T) {
{
name: "kase 4",
beforeCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/backport",
- },
- CreatedUnix: 10,
- },
+ labelComment(1, "1", "kind/bug", 0, nil, nil),
+ labelComment(1, "1", "kind/backport", 10, nil, nil),
},
afterCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- CreatedUnix: 10,
- AddedLabels: []*issues_model.Label{
- {
- Name: "kind/bug",
- },
- {
- Name: "kind/backport",
- },
- },
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- },
+ labelComment(1, "1", "kind/bug", 10, []*issues_model.Label{{Name: "kind/bug"}, {Name: "kind/backport"}}, nil),
},
},
{
name: "kase 5",
beforeCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeComment,
- PosterID: 2,
- Content: "testtest",
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, nil, nil),
+ comment(2, "testtest"),
+ labelComment(1, "", "kind/bug", 0, nil, nil),
},
afterCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- AddedLabels: []*issues_model.Label{
- {
- Name: "kind/bug",
- },
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeComment,
- PosterID: 2,
- Content: "testtest",
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "",
- RemovedLabels: []*issues_model.Label{
- {
- Name: "kind/bug",
- },
- },
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{{Name: "kind/bug"}}, nil),
+ comment(2, "testtest"),
+ labelComment(1, "", "kind/bug", 0, nil, []*issues_model.Label{{Name: "kind/bug"}}),
},
},
{
name: "kase 6",
beforeCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "reviewed/confirmed",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- CreatedUnix: 0,
- },
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/feature",
- },
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, nil, nil),
+ labelComment(1, "1", "reviewed/confirmed", 0, nil, nil),
+ labelComment(1, "", "kind/bug", 0, nil, nil),
+ labelComment(1, "1", "kind/feature", 0, nil, nil),
},
afterCombined: []*issues_model.Comment{
- {
- Type: issues_model.CommentTypeLabel,
- PosterID: 1,
- Content: "1",
- Label: &issues_model.Label{
- Name: "kind/bug",
- },
- AddedLabels: []*issues_model.Label{
- {
- Name: "reviewed/confirmed",
- },
- {
- Name: "kind/feature",
- },
- },
- CreatedUnix: 0,
- },
+ labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{{Name: "reviewed/confirmed"}, {Name: "kind/feature"}}, nil),
},
},
{
diff --git a/routers/web/repo/middlewares_test.go b/routers/web/repo/middlewares_test.go
index 0a16590ded5..f6a86924391 100644
--- a/routers/web/repo/middlewares_test.go
+++ b/routers/web/repo/middlewares_test.go
@@ -31,29 +31,19 @@ func TestDiffViewStyle(t *testing.T) {
})
t.Run("SignedInUser", func(t *testing.T) {
- ctx, _ := contexttest.MockContext(t, "/any")
- contexttest.LoadUser(t, ctx, 2)
- SetDiffViewStyle(ctx)
- assert.Equal(t, gitdiff.DiffStyleUnified, GetDiffViewStyle(ctx))
-
- ctx, _ = contexttest.MockContext(t, "/any?style=split")
- contexttest.LoadUser(t, ctx, 2)
- SetDiffViewStyle(ctx)
- assert.Equal(t, gitdiff.DiffStyleSplit, GetDiffViewStyle(ctx))
-
- ctx, _ = contexttest.MockContext(t, "/any")
- contexttest.LoadUser(t, ctx, 2)
- SetDiffViewStyle(ctx)
- assert.Equal(t, gitdiff.DiffStyleSplit, GetDiffViewStyle(ctx))
-
- ctx, _ = contexttest.MockContext(t, "/any?style=unified")
- contexttest.LoadUser(t, ctx, 2)
- SetDiffViewStyle(ctx)
- assert.Equal(t, gitdiff.DiffStyleUnified, GetDiffViewStyle(ctx))
-
- ctx, _ = contexttest.MockContext(t, "/any")
- contexttest.LoadUser(t, ctx, 2)
- SetDiffViewStyle(ctx)
- assert.Equal(t, gitdiff.DiffStyleUnified, GetDiffViewStyle(ctx))
+ for _, testCase := range []struct {
+ requestPath, expected string
+ }{
+ {"/any", gitdiff.DiffStyleUnified},
+ {"/any?style=split", gitdiff.DiffStyleSplit},
+ {"/any", gitdiff.DiffStyleSplit},
+ {"/any?style=unified", gitdiff.DiffStyleUnified},
+ {"/any", gitdiff.DiffStyleUnified},
+ } {
+ ctx, _ := contexttest.MockContext(t, testCase.requestPath)
+ contexttest.LoadUser(t, ctx, 2)
+ SetDiffViewStyle(ctx)
+ assert.Equal(t, testCase.expected, GetDiffViewStyle(ctx))
+ }
})
}
diff --git a/routers/web/repo/pull_review_test.go b/routers/web/repo/pull_review_test.go
index 691be213e61..40ed8de6ef5 100644
--- a/routers/web/repo/pull_review_test.go
+++ b/routers/web/repo/pull_review_test.go
@@ -53,40 +53,29 @@ func TestRenderConversation(t *testing.T) {
})
require.NotNil(t, preparedComment)
- run("diff with outdated", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
- ctx.Data["ShowOutdatedComments"] = true
- renderConversation(ctx, preparedComment, "diff")
- assert.Contains(t, resp.Body.String(), `
second line
third line", p.RichMessage.HTML) }) - t.Run("Create", func(t *testing.T) { - p := createTestPayload() - - pl, err := tc.Create(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] branch test created`, pl.RichMessage.HTML) - }) - - t.Run("Delete", func(t *testing.T) { - p := deleteTestPayload() - - pl, err := tc.Delete(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] branch test deleted`, pl.RichMessage.HTML) - }) - - t.Run("Fork", func(t *testing.T) { - p := forkTestPayload() - - pl, err := tc.Fork(p) - require.NoError(t, err) - - assert.Equal(t, `test/repo2 is forked to test/repo`, pl.RichMessage.HTML) - }) - - t.Run("Push", func(t *testing.T) { - p := pushTestPayload() - - pl, err := tc.Push(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo:test] 2 new commits
[2020558] commit message - user1
[2020558] commit message - user1`, pl.RichMessage.HTML) - }) + for _, testCase := range []struct { + name string + call func() (TelegramPayload, error) + text string + }{ + { + "Create", func() (TelegramPayload, error) { return tc.Create(createTestPayload()) }, + `[test/repo] branch test created`, + }, + { + "Delete", func() (TelegramPayload, error) { return tc.Delete(deleteTestPayload()) }, + `[test/repo] branch test deleted`, + }, + { + "Fork", func() (TelegramPayload, error) { return tc.Fork(forkTestPayload()) }, + `test/repo2 is forked to test/repo`, + }, + { + "Push", func() (TelegramPayload, error) { return tc.Push(pushTestPayload()) }, + `[test/repo:test] 2 new commits
[2020558] commit message - user1
[2020558] commit message - user1`, + }, + { + "IssueComment", func() (TelegramPayload, error) { return tc.IssueComment(issueCommentTestPayload()) }, + `[test/repo] New comment on issue #2 crash by user1
more info needed`, + }, + { + "PullRequest", func() (TelegramPayload, error) { return tc.PullRequest(pullRequestTestPayload()) }, + `[test/repo] Pull request opened: #12 Fix bug by user1
fixes bug #2`, + }, + { + "PullRequestComment", func() (TelegramPayload, error) { return tc.IssueComment(pullRequestCommentTestPayload()) }, + `[test/repo] New comment on pull request #12 Fix bug by user1
changes requested`, + }, + { + "Review", func() (TelegramPayload, error) { + p := pullRequestTestPayload() + p.Action = api.HookIssueReviewed + return tc.Review(p, webhook_module.HookEventPullRequestReviewApproved) + }, + `[test/repo] Pull request review approved: #12 Fix bug
good job`, + }, + { + "Repository", func() (TelegramPayload, error) { return tc.Repository(repositoryTestPayload()) }, + `[test/repo] Repository created`, + }, + { + "Package", func() (TelegramPayload, error) { return tc.Package(packageTestPayload()) }, + `Package created: GiteaContainer:latest by user1`, + }, + { + "Release", func() (TelegramPayload, error) { return tc.Release(pullReleaseTestPayload()) }, + `[test/repo] Release created: v1.0 by user1`, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + pl, err := testCase.call() + require.NoError(t, err) + assert.Equal(t, testCase.text, pl.RichMessage.HTML) + }) + } t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - - p.Action = api.HookIssueOpened - pl, err := tc.Issue(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Issue opened: #2 crash by user1
issue body`, pl.RichMessage.HTML) - - p.Action = api.HookIssueClosed - pl, err = tc.Issue(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Issue closed: #2 crash by user1`, pl.RichMessage.HTML) - }) - - t.Run("IssueComment", func(t *testing.T) { - p := issueCommentTestPayload() - - pl, err := tc.IssueComment(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] New comment on issue #2 crash by user1
more info needed`, pl.RichMessage.HTML) - }) - - t.Run("PullRequest", func(t *testing.T) { - p := pullRequestTestPayload() - - pl, err := tc.PullRequest(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Pull request opened: #12 Fix bug by user1
fixes bug #2`, pl.RichMessage.HTML) - }) - - t.Run("PullRequestComment", func(t *testing.T) { - p := pullRequestCommentTestPayload() - - pl, err := tc.IssueComment(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] New comment on pull request #12 Fix bug by user1
changes requested`, pl.RichMessage.HTML) - }) - - t.Run("Review", func(t *testing.T) { - p := pullRequestTestPayload() - p.Action = api.HookIssueReviewed - - pl, err := tc.Review(p, webhook_module.HookEventPullRequestReviewApproved) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug
good job`, pl.RichMessage.HTML) - }) - - t.Run("Repository", func(t *testing.T) { - p := repositoryTestPayload() - - pl, err := tc.Repository(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Repository created`, pl.RichMessage.HTML) - }) - - t.Run("Package", func(t *testing.T) { - p := packageTestPayload() - - pl, err := tc.Package(p) - require.NoError(t, err) - - assert.Equal(t, `Package created: GiteaContainer:latest by user1`, pl.RichMessage.HTML) + for _, testCase := range []struct { + action api.HookIssueAction + text string + }{ + {api.HookIssueOpened, `[test/repo] Issue opened: #2 crash by user1
issue body`}, + {api.HookIssueClosed, `[test/repo] Issue closed: #2 crash by user1`}, + } { + p.Action = testCase.action + pl, err := tc.Issue(p) + require.NoError(t, err) + assert.Equal(t, testCase.text, pl.RichMessage.HTML) + } }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - - p.Action = api.HookWikiCreated - pl, err := tc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] New wiki page 'index' (Wiki change comment) by user1`, pl.RichMessage.HTML) - - p.Action = api.HookWikiEdited - pl, err = tc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Wiki page 'index' edited (Wiki change comment) by user1`, pl.RichMessage.HTML) - - p.Action = api.HookWikiDeleted - pl, err = tc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Wiki page 'index' deleted by user1`, pl.RichMessage.HTML) - }) - - t.Run("Release", func(t *testing.T) { - p := pullReleaseTestPayload() - - pl, err := tc.Release(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Release created: v1.0 by user1`, pl.RichMessage.HTML) + for _, testCase := range []struct { + action api.HookWikiAction + text string + }{ + {api.HookWikiCreated, `[test/repo] New wiki page 'index' (Wiki change comment) by user1`}, + {api.HookWikiEdited, `[test/repo] Wiki page 'index' edited (Wiki change comment) by user1`}, + {api.HookWikiDeleted, `[test/repo] Wiki page 'index' deleted by user1`}, + } { + p.Action = testCase.action + pl, err := tc.Wiki(p) + require.NoError(t, err) + assert.Equal(t, testCase.text, pl.RichMessage.HTML) + } }) } diff --git a/tests/integration/actions_concurrency_test.go b/tests/integration/actions_concurrency_test.go index 95ae1ee38ca..658cb820aca 100644 --- a/tests/integration/actions_concurrency_test.go +++ b/tests/integration/actions_concurrency_test.go @@ -30,333 +30,108 @@ import ( ) func TestWorkflowConcurrency(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - // add a variable for test - req := NewRequestWithJSON(t, "POST", - fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{ - Value: "abc123", - }). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml" - wf1FileContent := `name: concurrent-workflow-1 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-1.yml' -concurrency: - group: workflow-main-abc123-user2 -jobs: - wf1-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow1' -` - wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml" - wf2FileContent := `name: concurrent-workflow-2 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-2.yml' -concurrency: - group: workflow-${{ gitea.ref_name }}-${{ vars.myvar }}-${{ gitea.event.pusher.username }} -jobs: - wf2-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow2' -` - wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml" - wf3FileContent := `name: concurrent-workflow-3 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-3.yml' -concurrency: - group: workflow-main-abc${{ 123 }}-${{ gitea.event.pusher.username }} -jobs: - wf3-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow3' -` - // push workflow1 - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - // fetch and exec workflow1 - task := runner.fetchTask(t) - _, _, run := getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // push workflow2 - opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2) - // fetch workflow2 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID) - - // push workflow3 - opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3) - runner.fetchNoTask(t) - - // exec workflow2 - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // fetch and exec workflow3 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - }) -} - -func TestWorkflowConcurrencyShort(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - // add a variable for test - req := NewRequestWithJSON(t, "POST", - fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{ - Value: "abc123", - }). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml" - wf1FileContent := `name: concurrent-workflow-1 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-1.yml' -concurrency: workflow-main-abc123-user2 -jobs: - wf1-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow1' -` - wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml" - wf2FileContent := `name: concurrent-workflow-2 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-2.yml' -concurrency: workflow-${{ gitea.ref_name }}-${{ vars.myvar }}-${{ gitea.event.pusher.username }} -jobs: - wf2-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow2' -` - wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml" - wf3FileContent := `name: concurrent-workflow-3 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-3.yml' -concurrency: workflow-main-abc${{ 123 }}-${{ gitea.event.pusher.username }} -jobs: - wf3-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow3' -` - // push workflow1 - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - // fetch and exec workflow1 - task := runner.fetchTask(t) - _, _, run := getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // push workflow2 - opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2) - // fetch workflow2 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID) - - // push workflow3 - opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3) - runner.fetchNoTask(t) - - // exec workflow2 - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // fetch and exec workflow3 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - }) -} - -func TestWorkflowConcurrencyShortJson(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - // add a variable for test - req := NewRequestWithJSON(t, "POST", - fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{ - Value: "abc123", - }). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml" - wf1FileContent := `name: concurrent-workflow-1 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-1.yml' -concurrency: |- + for _, workflowSyntax := range []struct { + name, concurrency string + }{ + {"Long", `concurrency: + group: %s`}, + {"Short", `concurrency: %s`}, + {"ShortJson", `concurrency: |- ${{ fromjson('{ - "group": "workflow-main-abc123-user2", + "group": "%s", "cancel-in-progress": false - }') }} -jobs: - wf1-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow1' -` - wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml" - wf2FileContent := `name: concurrent-workflow-2 + }') }}`}, + } { + t.Run(workflowSyntax.name, func(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) + defer doAPIDeleteRepository(httpContext)(t) + + runner := newMockRunner() + runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + + // add a variable for test + req := NewRequestWithJSON(t, "POST", + fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{ + Value: "abc123", + }). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + workflow := func(index int, group string) string { + if workflowSyntax.name == "ShortJson" { + group = "workflow-main-abc123-user2" + } + return fmt.Sprintf(`name: concurrent-workflow-%[1]d on: push: paths: - - '.gitea/workflows/concurrent-workflow-2.yml' -concurrency: |- - ${{ fromjson('{ - "group": "workflow-main-abc123-user2", - "cancel-in-progress": false - }') }} + - '.gitea/workflows/concurrent-workflow-%[1]d.yml' +%[2]s jobs: - wf2-job: + wf%[1]d-job: runs-on: ubuntu-latest steps: - - run: echo 'job from workflow2' -` - wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml" - wf3FileContent := `name: concurrent-workflow-3 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-3.yml' -concurrency: |- - ${{ fromjson('{ - "group": "workflow-main-abc123-user2", - "cancel-in-progress": false - }') }} -jobs: - wf3-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow3' -` - // push workflow1 - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - // fetch and exec workflow1 - task := runner.fetchTask(t) - _, _, run := getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, + - run: echo 'job from workflow%[1]d' +`, index, fmt.Sprintf(workflowSyntax.concurrency, group)) + } + + wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml" + wf1FileContent := workflow(1, "workflow-main-abc123-user2") + wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml" + wf2FileContent := workflow(2, "workflow-${{ gitea.ref_name }}-${{ vars.myvar }}-${{ gitea.event.pusher.username }}") + wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml" + wf3FileContent := workflow(3, "workflow-main-abc${{ 123 }}-${{ gitea.event.pusher.username }}") + // push workflow1 + opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) + // fetch and exec workflow1 + task := runner.fetchTask(t) + _, _, run := getTaskAndJobAndRunByTaskID(t, task.Id) + assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) + assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID) + runner.fetchNoTask(t) + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + + // push workflow2 + opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2) + // fetch workflow2 + task = runner.fetchTask(t) + _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) + assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) + assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID) + + // push workflow3 + opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3) + runner.fetchNoTask(t) + + // exec workflow2 + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + + // fetch and exec workflow3 + task = runner.fetchTask(t) + _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) + assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) + assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID) + runner.fetchNoTask(t) + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + }) }) - - // push workflow2 - opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2) - // fetch workflow2 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID) - - // push workflow3 - opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3) - runner.fetchNoTask(t) - - // exec workflow2 - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // fetch and exec workflow3 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - }) + } } func TestPullRequestWorkflowConcurrency(t *testing.T) { @@ -850,22 +625,24 @@ jobs: }) } -func TestWorkflowDispatchRerunAllJobsConcurrency(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) +func TestWorkflowDispatchRerunConcurrency(t *testing.T) { + for _, jobScope := range []string{"all-jobs", "single-job"} { + t.Run(jobScope, func(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) + apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) + defer doAPIDeleteRepository(httpContext)(t) - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + runner := newMockRunner() + runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - wf1TreePath := ".gitea/workflows/workflow-dispatch-concurrency.yml" - wf1FileContent := `name: workflow-dispatch-concurrency + wf1TreePath := ".gitea/workflows/workflow-dispatch-concurrency.yml" + wf1FileContent := `name: workflow-dispatch-concurrency on: workflow_dispatch: inputs: @@ -893,236 +670,110 @@ jobs: - run: echo 'workflow dispatch job' ` - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) + opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - // run the workflow with appVersion=v1.21 and cancel=false - urlStr := fmt.Sprintf("/%s/%s/actions/run?workflow=%s", user2.Name, repo.Name, "workflow-dispatch-concurrency.yml") - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.21", + // run the workflow with appVersion=v1.21 and cancel=false + urlStr := fmt.Sprintf("/%s/%s/actions/run?workflow=%s", user2.Name, repo.Name, "workflow-dispatch-concurrency.yml") + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "ref": "refs/heads/main", + "appVersion": "v1.21", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + task1 := runner.fetchTask(t) + _, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id) + assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1)) + + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "ref": "refs/heads/main", + "appVersion": "v1.22", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + task2 := runner.fetchTask(t) + _, job2, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id) + assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2)) + + // run the workflow with appVersion=v1.22 and cancel=false again + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "ref": "refs/heads/main", + "appVersion": "v1.22", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + runner.fetchNoTask(t) // cannot fetch task because task2 is not completed + run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID}, unittest.OrderBy("id DESC")) + assert.Equal(t, actions_model.StatusBlocked, run3.Status) + job3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{RepoID: repo.ID, RunID: run3.ID}) + assert.Equal(t, actions_model.StatusBlocked, job3.Status) + + // run the workflow with appVersion=v1.22 and cancel=true + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "ref": "refs/heads/main", + "appVersion": "v1.22", + "cancel": "on", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + task4 := runner.fetchTask(t) + _, job4, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id) + assert.Equal(t, actions_model.StatusRunning, run4.Status) + assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4)) + _, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id) + assert.Equal(t, actions_model.StatusCancelled, run2.Status) + + runner.execTask(t, task4, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + + rerun := func(runID int64, job **actions_model.ActionRunJob) { + if jobScope == "single-job" { + *job = getLatestAttemptJobByTemplateJobID(t, runID, (*job).ID) + req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, runID, (*job).ID)) + } else { + req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, runID)) + } + _ = session.MakeRequest(t, req, http.StatusOK) + } + + // rerun cancel true scenario + + rerun(run2.ID, &job2) + + rerun(run4.ID, &job4) + + task5 := runner.fetchTask(t) + _, _, run4_1 := getTaskAndJobAndRunByTaskID(t, task5.Id) + assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4_1)) + assert.Equal(t, run4.ID, run4_1.ID) + _, _, run2_1 := getTaskAndJobAndRunByTaskID(t, task2.Id) + assert.Equal(t, actions_model.StatusCancelled, run2_1.Status) + + runner.execTask(t, task5, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_CANCELLED, + }) + + // rerun cancel false scenario + + rerun(run2.ID, &job2) + + run2_2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID}) + assert.Equal(t, actions_model.StatusWaiting, run2_2.Status) + + rerun(run3.ID, &job3) + + assert.Equal(t, actions_model.StatusBlocked, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run3.ID}).Status) + runner.execTask(t, runner.fetchTask(t), &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS}) + task6 := runner.fetchTask(t) + _, _, run3_2 := getTaskAndJobAndRunByTaskID(t, task6.Id) + if jobScope == "all-jobs" { + assert.Equal(t, run3.ID, run3_2.ID) + assert.Equal(t, actions_model.StatusRunning, run3_2.Status) + } else { + run3 = run3_2 + } + assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run3)) + }) }) - session.MakeRequest(t, req, http.StatusSeeOther) - task1 := runner.fetchTask(t) - _, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id) - assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1)) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task2 := runner.fetchTask(t) - _, _, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2)) - - // run the workflow with appVersion=v1.22 and cancel=false again - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - runner.fetchNoTask(t) // cannot fetch task because task2 is not completed - run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID}, unittest.OrderBy("id DESC")) - assert.Equal(t, actions_model.StatusBlocked, run3.Status) - job3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{RepoID: repo.ID, RunID: run3.ID}) - assert.Equal(t, actions_model.StatusBlocked, job3.Status) - - // run the workflow with appVersion=v1.22 and cancel=true - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - "cancel": "on", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task4 := runner.fetchTask(t) - _, _, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id) - assert.Equal(t, actions_model.StatusRunning, run4.Status) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4)) - _, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, actions_model.StatusCancelled, run2.Status) - - runner.execTask(t, task4, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // rerun cancel true scenario - - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, run2.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, run4.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - task5 := runner.fetchTask(t) - _, _, run4_1 := getTaskAndJobAndRunByTaskID(t, task5.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4_1)) - assert.Equal(t, run4.ID, run4_1.ID) - _, _, run2_1 := getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, actions_model.StatusCancelled, run2_1.Status) - - runner.execTask(t, task5, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_CANCELLED, - }) - - // rerun cancel false scenario - - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, run2.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - run2_2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID}) - assert.Equal(t, actions_model.StatusWaiting, run2_2.Status) - - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, run3.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, actions_model.StatusBlocked, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run3.ID}).Status) - runner.execTask(t, runner.fetchTask(t), &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS}) - task6 := runner.fetchTask(t) - _, _, run3_2 := getTaskAndJobAndRunByTaskID(t, task6.Id) - assert.Equal(t, run3.ID, run3_2.ID) - assert.Equal(t, actions_model.StatusRunning, run3_2.Status) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run3)) - }) -} - -func TestWorkflowDispatchRerunSingleJobConcurrency(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - wf1TreePath := ".gitea/workflows/workflow-dispatch-concurrency.yml" - wf1FileContent := `name: workflow-dispatch-concurrency -on: - workflow_dispatch: - inputs: - appVersion: - description: 'APP version' - required: true - default: 'v1.23' - type: choice - options: - - v1.21 - - v1.22 - - v1.23 - cancel: - description: 'Cancel running workflows' - required: false - type: boolean - default: false -concurrency: - group: workflow-dispatch-${{ inputs.appVersion }} - cancel-in-progress: ${{ inputs.cancel }} -jobs: - job: - runs-on: ubuntu-latest - steps: - - run: echo 'workflow dispatch job' -` - - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - - // run the workflow with appVersion=v1.21 and cancel=false - urlStr := fmt.Sprintf("/%s/%s/actions/run?workflow=%s", user2.Name, repo.Name, "workflow-dispatch-concurrency.yml") - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.21", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task1 := runner.fetchTask(t) - _, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id) - assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1)) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task2 := runner.fetchTask(t) - _, job2, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2)) - - // run the workflow with appVersion=v1.22 and cancel=false again - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - runner.fetchNoTask(t) // cannot fetch task because task2 is not completed - run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID}, unittest.OrderBy("id DESC")) - assert.Equal(t, actions_model.StatusBlocked, run3.Status) - job3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{RepoID: repo.ID, RunID: run3.ID}) - assert.Equal(t, actions_model.StatusBlocked, job3.Status) - - // run the workflow with appVersion=v1.22 and cancel=true - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - "cancel": "on", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task4 := runner.fetchTask(t) - _, job4, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id) - assert.Equal(t, actions_model.StatusRunning, run4.Status) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4)) - _, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, actions_model.StatusCancelled, run2.Status) - - runner.execTask(t, task4, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // rerun cancel true scenario - job2 = getLatestAttemptJobByTemplateJobID(t, run2.ID, job2.ID) - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run2.ID, job2.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - job4 = getLatestAttemptJobByTemplateJobID(t, run4.ID, job4.ID) - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run4.ID, job4.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - task5 := runner.fetchTask(t) - _, _, run4_1 := getTaskAndJobAndRunByTaskID(t, task5.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4_1)) - assert.Equal(t, run4.ID, run4_1.ID) - _, _, run2_1 := getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, actions_model.StatusCancelled, run2_1.Status) - - runner.execTask(t, task5, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_CANCELLED, - }) - - // rerun cancel false scenario - - job2 = getLatestAttemptJobByTemplateJobID(t, run2.ID, job2.ID) - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run2.ID, job2.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - run2_2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID}) - assert.Equal(t, actions_model.StatusWaiting, run2_2.Status) - - job3 = getLatestAttemptJobByTemplateJobID(t, run3.ID, job3.ID) - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run3.ID, job3.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, actions_model.StatusBlocked, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run3.ID}).Status) - runner.execTask(t, runner.fetchTask(t), &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS}) - task6 := runner.fetchTask(t) - _, _, run3 = getTaskAndJobAndRunByTaskID(t, task6.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run3)) - }) + } } func TestScheduleConcurrency(t *testing.T) { diff --git a/tests/integration/actions_delete_run_test.go b/tests/integration/actions_delete_run_test.go index 39f0bd712b7..b577457eb6e 100644 --- a/tests/integration/actions_delete_run_test.go +++ b/tests/integration/actions_delete_run_test.go @@ -25,6 +25,26 @@ import ( func TestActionsDeleteRun(t *testing.T) { now := time.Now() + outcomes := make(map[string]*mockTaskOutcome) + for _, jobName := range []string{"job1", "job2", "job3"} { + outcomes[jobName] = &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(4 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(5 * time.Second)), + Content: jobName, + }, + { + Time: timestamppb.New(now.Add(6 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, + }, + } + } testCase := struct { treePath string fileContent string @@ -51,59 +71,7 @@ jobs: steps: - run: echo job3 `, - outcomes: map[string]*mockTaskOutcome{ - "job1": { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job1", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - "job2": { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job2", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - "job3": { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job3", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - }, + outcomes: outcomes, expectedStatuses: map[string]string{ "job1": actions_model.StatusSuccess.String(), "job2": actions_model.StatusSuccess.String(), diff --git a/tests/integration/actions_job_test.go b/tests/integration/actions_job_test.go index b2c46d024aa..7a1da909abd 100644 --- a/tests/integration/actions_job_test.go +++ b/tests/integration/actions_job_test.go @@ -466,21 +466,29 @@ func getRepoRunnerID(t *testing.T, authToken, ownerName, repoName string) int64 } func TestActionsGiteaContext(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user2Session := loginUser(t, user2.Name) - user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + for _, ephemeral := range []bool{false, true} { + t.Run(strconv.FormatBool(ephemeral), func(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2Session := loginUser(t, user2.Name) + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) - baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) - runner := newMockRunner() - runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + runner := newMockRunner() + runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, ephemeral) - // init the workflow - wfTreePath := ".gitea/workflows/pull.yml" - wfFileContent := `name: Pull Request + if ephemeral { + // verify CleanupEphemeralRunners does not remove this runner + err := actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + } + + // init the workflow + wfTreePath := ".gitea/workflows/pull.yml" + wfFileContent := `name: Pull Request on: pull_request jobs: wf1-job: @@ -488,213 +496,132 @@ jobs: steps: - run: echo 'test the pull' ` - opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) - // user2 creates a pull request - doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - NewBranchName: "user2/patch-1", - Message: "create user2-patch.txt", - Author: api.Identity{ - Name: user2.Name, - Email: user2.Email, - }, - Committer: api.Identity{ - Name: user2.Name, - Email: user2.Email, - }, - Dates: api.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")), - })(t) - apiPull, err := doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "user2/patch-1")(t) - assert.NoError(t, err) - task := runner.fetchTask(t) - gtCtx := task.Context.GetFields() - actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) - actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) - actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) - assert.NoError(t, actionRun.LoadAttributes(t.Context())) - - assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) - assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) - assert.Equal(t, apiPull.Base.Ref, gtCtx["base_ref"].GetStringValue()) - runEvent := map[string]any{} - assert.NoError(t, json.Unmarshal([]byte(actionRun.EventPayload), &runEvent)) - assert.True(t, reflect.DeepEqual(gtCtx["event"].GetStructValue().AsMap(), runEvent)) - assert.Equal(t, actionRun.TriggerEvent, gtCtx["event_name"].GetStringValue()) - assert.Equal(t, apiPull.Head.Ref, gtCtx["head_ref"].GetStringValue()) - assert.Equal(t, actionRunJob.JobID, gtCtx["job"].GetStringValue()) - assert.Equal(t, actionRun.Ref, gtCtx["ref"].GetStringValue()) - assert.Equal(t, git.RefName(actionRun.Ref).ShortName(), gtCtx["ref_name"].GetStringValue()) - assert.False(t, gtCtx["ref_protected"].GetBoolValue()) - assert.Equal(t, "branch", gtCtx["ref_type"].GetStringValue()) - assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) - assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) - assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) - assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) - assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) - assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) - assert.Equal(t, actionRun.WorkflowID, gtCtx["workflow"].GetStringValue()) - assert.Equal(t, setting.Actions.DefaultActionsURL.URL(), gtCtx["gitea_default_actions_url"].GetStringValue()) - token := gtCtx["token"].GetStringValue() - assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) - }) -} - -// Ephemeral -func TestActionsGiteaContextEphemeral(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user2Session := loginUser(t, user2.Name) - user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) - baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, true) - - // verify CleanupEphemeralRunners does not remove this runner - err := actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) - - // init the workflow - wfTreePath := ".gitea/workflows/pull.yml" - wfFileContent := `name: Pull Request -on: pull_request -jobs: - wf1-job: - runs-on: ubuntu-latest - steps: - - run: echo 'test the pull' - wf2-job: + if ephemeral { + wfFileContent += ` wf2-job: runs-on: ubuntu-latest steps: - run: echo 'test the pull' ` - opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) - // user2 creates a pull request - doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - NewBranchName: "user2/patch-1", - Message: "create user2-patch.txt", - Author: api.Identity{ - Name: user2.Name, - Email: user2.Email, - }, - Committer: api.Identity{ - Name: user2.Name, - Email: user2.Email, - }, - Dates: api.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")), - })(t) - apiPull, err := doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "user2/patch-1")(t) - assert.NoError(t, err) - task := runner.fetchTask(t) - gtCtx := task.Context.GetFields() - actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) - actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) - actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) - assert.NoError(t, actionRun.LoadAttributes(t.Context())) + } + opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) + // user2 creates a pull request + doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: "user2/patch-1", + Message: "create user2-patch.txt", + Author: api.Identity{ + Name: user2.Name, + Email: user2.Email, + }, + Committer: api.Identity{ + Name: user2.Name, + Email: user2.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")), + })(t) + apiPull, err := doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "user2/patch-1")(t) + assert.NoError(t, err) + task := runner.fetchTask(t) + gtCtx := task.Context.GetFields() + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) + actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) + actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) + assert.NoError(t, actionRun.LoadAttributes(t.Context())) - assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) - assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) - assert.Equal(t, apiPull.Base.Ref, gtCtx["base_ref"].GetStringValue()) - runEvent := map[string]any{} - assert.NoError(t, json.Unmarshal([]byte(actionRun.EventPayload), &runEvent)) - assert.True(t, reflect.DeepEqual(gtCtx["event"].GetStructValue().AsMap(), runEvent)) - assert.Equal(t, actionRun.TriggerEvent, gtCtx["event_name"].GetStringValue()) - assert.Equal(t, apiPull.Head.Ref, gtCtx["head_ref"].GetStringValue()) - assert.Equal(t, actionRunJob.JobID, gtCtx["job"].GetStringValue()) - assert.Equal(t, actionRun.Ref, gtCtx["ref"].GetStringValue()) - assert.Equal(t, git.RefName(actionRun.Ref).ShortName(), gtCtx["ref_name"].GetStringValue()) - assert.False(t, gtCtx["ref_protected"].GetBoolValue()) - assert.Equal(t, "branch", gtCtx["ref_type"].GetStringValue()) - assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) - assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) - assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) - assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) - assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) - assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) - assert.Equal(t, actionRun.WorkflowID, gtCtx["workflow"].GetStringValue()) - assert.Equal(t, setting.Actions.DefaultActionsURL.URL(), gtCtx["gitea_default_actions_url"].GetStringValue()) - token := gtCtx["token"].GetStringValue() - assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) + assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) + assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) + assert.Equal(t, apiPull.Base.Ref, gtCtx["base_ref"].GetStringValue()) + runEvent := map[string]any{} + assert.NoError(t, json.Unmarshal([]byte(actionRun.EventPayload), &runEvent)) + assert.True(t, reflect.DeepEqual(gtCtx["event"].GetStructValue().AsMap(), runEvent)) + assert.Equal(t, actionRun.TriggerEvent, gtCtx["event_name"].GetStringValue()) + assert.Equal(t, apiPull.Head.Ref, gtCtx["head_ref"].GetStringValue()) + assert.Equal(t, actionRunJob.JobID, gtCtx["job"].GetStringValue()) + assert.Equal(t, actionRun.Ref, gtCtx["ref"].GetStringValue()) + assert.Equal(t, git.RefName(actionRun.Ref).ShortName(), gtCtx["ref_name"].GetStringValue()) + assert.False(t, gtCtx["ref_protected"].GetBoolValue()) + assert.Equal(t, "branch", gtCtx["ref_type"].GetStringValue()) + assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) + assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) + assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) + assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) + assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) + assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) + assert.Equal(t, actionRun.WorkflowID, gtCtx["workflow"].GetStringValue()) + assert.Equal(t, setting.Actions.DefaultActionsURL.URL(), gtCtx["gitea_default_actions_url"].GetStringValue()) + token := gtCtx["token"].GetStringValue() + assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) - // verify CleanupEphemeralRunners does not remove this runner - err = actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) + if ephemeral { + // verify CleanupEphemeralRunners does not remove this runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) - resp, err := runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ - TasksVersion: 0, + resp, err := runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ + TasksVersion: 0, + }) + assert.NoError(t, err) + assert.Nil(t, resp.Task) + + // verify CleanupEphemeralRunners does not remove this runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + _, err = runner.client.runnerServiceClient.UpdateTask(t.Context(), &runnerv1.UpdateTaskRequest{ + State: &runnerv1.TaskState{ + Id: actionTask.ID, + Result: runnerv1.Result_RESULT_SUCCESS, + }, + }) + assert.NoError(t, err) + + resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ + TasksVersion: 0, + }) + assert.Error(t, err) + assert.Nil(t, resp) + + resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ + TasksVersion: 0, + }) + assert.Error(t, err) + assert.Nil(t, resp) + + // create a runner that picks a job and get force cancelled + runnerToBeRemoved := newMockRunner() + runnerToBeRemoved.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner-to-be-removed", []string{"ubuntu-latest"}, true) + + taskToStopAPIObj := runnerToBeRemoved.fetchTask(t) + + taskToStop := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: taskToStopAPIObj.Id}) + + // verify CleanupEphemeralRunners does not remove the custom crafted runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + runnerToRemove := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: taskToStop.RunnerID}) + + err = actions_model.StopTask(t.Context(), taskToStop.ID, actions_model.StatusFailure) + assert.NoError(t, err) + + // verify CleanupEphemeralRunners does remove the custom crafted runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: runnerToRemove.ID}) + } + }) }) - assert.NoError(t, err) - assert.Nil(t, resp.Task) - - // verify CleanupEphemeralRunners does not remove this runner - err = actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) - - _, err = runner.client.runnerServiceClient.UpdateTask(t.Context(), &runnerv1.UpdateTaskRequest{ - State: &runnerv1.TaskState{ - Id: actionTask.ID, - Result: runnerv1.Result_RESULT_SUCCESS, - }, - }) - assert.NoError(t, err) - - resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ - TasksVersion: 0, - }) - assert.Error(t, err) - assert.Nil(t, resp) - - resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ - TasksVersion: 0, - }) - assert.Error(t, err) - assert.Nil(t, resp) - - // create a runner that picks a job and get force cancelled - runnerToBeRemoved := newMockRunner() - runnerToBeRemoved.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner-to-be-removed", []string{"ubuntu-latest"}, true) - - taskToStopAPIObj := runnerToBeRemoved.fetchTask(t) - - taskToStop := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: taskToStopAPIObj.Id}) - - // verify CleanupEphemeralRunners does not remove the custom crafted runner - err = actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) - - runnerToRemove := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: taskToStop.RunnerID}) - - err = actions_model.StopTask(t.Context(), taskToStop.ID, actions_model.StatusFailure) - assert.NoError(t, err) - - // verify CleanupEphemeralRunners does remove the custom crafted runner - err = actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) - - unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: runnerToRemove.ID}) - }) + } } func createActionsTestRepo(t *testing.T, authToken, repoName string, isPrivate bool) *api.Repository { diff --git a/tests/integration/actions_job_token_test.go b/tests/integration/actions_job_token_test.go index e85201e8299..12cc48a95b5 100644 --- a/tests/integration/actions_job_token_test.go +++ b/tests/integration/actions_job_token_test.go @@ -183,103 +183,210 @@ func TestActionsJobTokenPermissiveAccess(t *testing.T) { }) }) } - }) -} -func TestActionsCrossRepoAccess(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization) + t.Run("CrossRepoAccess", func(t *testing.T) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization) - // 1. Create Organization - orgName := "org-cross-test" - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &structs.CreateOrgOption{ - UserName: orgName, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - owner, err := org_model.GetOrgByName(t.Context(), orgName) - require.NoError(t, err) - - // 2. Create Two Repositories in owner - createRepoInOrg := func(name string) int64 { - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/repos", orgName), &structs.CreateRepoOption{ - Name: name, - AutoInit: true, - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - repo := DecodeJSON(t, resp, &structs.Repository{}) - return repo.ID - } - - repoAID := createRepoInOrg("repo-A") - repoBID := createRepoInOrg("repo-B") - - // 3. Enable Actions in Repo A (Source) and Repo B (Target) - enableActions := func(repoID int64) { - err := db.Insert(t.Context(), &repo_model.RepoUnit{ - RepoID: repoID, - Type: unit_model.TypeActions, - Config: &repo_model.ActionsConfig{ - TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive, - }, - }) - require.NoError(t, err) - } - - enableActions(repoAID) - enableActions(repoBID) - - // 4. Create Task in Repo A, and use A's token to access B - taskA := createActionTask(t, repoAID, false) - testCtxA := APITestContext{ - Session: emptyTestSession(t), - Token: taskA.Token, - Username: orgName, - Reponame: "repo-B", - } - - testCtxA.ExpectedCode = http.StatusOK - t.Run("PublicCrossRepoAccess", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) { - assert.Equal(t, "repo-B", r.Name) - })) - - // make repo-B be private - req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/org-cross-test/repo-B", &structs.EditRepoOption{Private: new(true)}).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - - testCtxA.ExpectedCode = http.StatusNotFound - t.Run("NoPrivateCrossRepoAccess", doAPIGetRepository(testCtxA, nil)) - - ownerActionsCfg := actions_model.OwnerActionsConfig{AllowedCrossRepoIDs: []int64{repoBID}} - require.NoError(t, actions_model.SetOwnerActionsConfig(t.Context(), owner.ID, ownerActionsCfg)) - - testCtxA.ExpectedCode = http.StatusOK - t.Run("AccessToSelectedPrivateRepo", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) { - assert.Equal(t, "repo-B", r.Name) - })) - - t.Run("RepoTransfer", func(t *testing.T) { - ownerActionsCfg, err := actions_model.GetOwnerActionsConfig(t.Context(), owner.ID) - require.NoError(t, err) - assert.Contains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID) - - // Transfer Repository to user4 - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer", orgName), &structs.TransferRepoOption{ - NewOwner: "user4", + // 1. Create Organization + orgName := "org-cross-test" + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &structs.CreateOrgOption{ + UserName: orgName, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - // Accept transfer as user4 - session4 := loginUser(t, "user4") - token4 := getTokenForLoggedInUser(t, session4, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer/accept", orgName)).AddTokenAuth(token4) - MakeRequest(t, req, http.StatusAccepted) - - // Verify it is removed from the org's config - ownerActionsCfg, err = actions_model.GetOwnerActionsConfig(t.Context(), owner.ID) + owner, err := org_model.GetOrgByName(t.Context(), orgName) require.NoError(t, err) - assert.NotContains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID) + + // 2. Create Two Repositories in owner + createRepoInOrg := func(name string) int64 { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/repos", orgName), &structs.CreateRepoOption{ + Name: name, + AutoInit: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + repo := DecodeJSON(t, resp, &structs.Repository{}) + return repo.ID + } + + repoAID := createRepoInOrg("repo-A") + repoBID := createRepoInOrg("repo-B") + + // 3. Enable Actions in Repo A (Source) and Repo B (Target) + enableActions := func(repoID int64) { + err := db.Insert(t.Context(), &repo_model.RepoUnit{ + RepoID: repoID, + Type: unit_model.TypeActions, + Config: &repo_model.ActionsConfig{ + TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive, + }, + }) + require.NoError(t, err) + } + + enableActions(repoAID) + enableActions(repoBID) + + // 4. Create Task in Repo A, and use A's token to access B + taskA := createActionTask(t, repoAID, false) + testCtxA := APITestContext{ + Session: emptyTestSession(t), + Token: taskA.Token, + Username: orgName, + Reponame: "repo-B", + } + + testCtxA.ExpectedCode = http.StatusOK + t.Run("PublicCrossRepoAccess", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) { + assert.Equal(t, "repo-B", r.Name) + })) + + // make repo-B be private + req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/org-cross-test/repo-B", &structs.EditRepoOption{Private: new(true)}).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + testCtxA.ExpectedCode = http.StatusNotFound + t.Run("NoPrivateCrossRepoAccess", doAPIGetRepository(testCtxA, nil)) + + ownerActionsCfg := actions_model.OwnerActionsConfig{AllowedCrossRepoIDs: []int64{repoBID}} + require.NoError(t, actions_model.SetOwnerActionsConfig(t.Context(), owner.ID, ownerActionsCfg)) + + testCtxA.ExpectedCode = http.StatusOK + t.Run("AccessToSelectedPrivateRepo", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) { + assert.Equal(t, "repo-B", r.Name) + })) + + t.Run("RepoTransfer", func(t *testing.T) { + ownerActionsCfg, err := actions_model.GetOwnerActionsConfig(t.Context(), owner.ID) + require.NoError(t, err) + assert.Contains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID) + + // Transfer Repository to user4 + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer", orgName), &structs.TransferRepoOption{ + NewOwner: "user4", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + // Accept transfer as user4 + session4 := loginUser(t, "user4") + token4 := getTokenForLoggedInUser(t, session4, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer/accept", orgName)).AddTokenAuth(token4) + MakeRequest(t, req, http.StatusAccepted) + + // Verify it is removed from the org's config + ownerActionsCfg, err = actions_model.GetOwnerActionsConfig(t.Context(), owner.ID) + require.NoError(t, err) + assert.NotContains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID) + }) + }) + + t.Run("TokenPermissionsPersistenceWithWorkflow", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + // create repos + repo1 := createActionsTestRepo(t, token, "actions-permission-repo1", false) + repo2 := createActionsTestRepo(t, token, "actions-permission-repo2", true) + + // add repo2 to owner-level cross-repo access list + req := NewRequestWithValues(t, "POST", "/user/settings/actions/general", map[string]string{ + "cross_repo_add_target": "true", + "cross_repo_add_target_name": repo2.Name, + }) + session.MakeRequest(t, req, http.StatusOK) + + // create the runner for repo1 + runner1 := newMockRunner() + runner1.registerAsRepoRunner(t, user2.Name, repo1.Name, "mock-runner", []string{"ubuntu-latest"}, false) + + // set repo1 actions token permission mode to "permissive" + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{ + "token_permission_mode": "permissive", + "override_owner_config": "true", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // set repo2 actions token permission mode to "restricted", and set max permissions + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ + "token_permission_mode": "restricted", + "override_owner_config": "true", + "enable_max_permissions": "true", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // create a workflow file with "permission" keyword for repo1 + wfTreePath := ".gitea/workflows/test_permissions.yml" + wfFileContent := `name: Test Permissions +on: + push: + paths: + - '.gitea/workflows/test_permissions.yml' + +jobs: + job-override: + runs-on: ubuntu-latest + permissions: + code: write + steps: + - run: echo "test perms" +` + opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, token, user2.Name, repo1.Name, wfTreePath, opts) + + task1 := runner1.fetchTask(t) + task1Token := task1.Secrets["GITEA_TOKEN"] + require.NotEmpty(t, task1Token) + + // should fail: target repo does not allow code access + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token) + MakeRequest(t, req, http.StatusNotFound) + + // set repo2 max permission to "read" so that the actions token can access code + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ + "token_permission_mode": "restricted", + "override_owner_config": "true", + "enable_max_permissions": "true", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "read", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // should succeed: target repo now allows code read access for this token + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token) + MakeRequest(t, req, http.StatusOK) + // but it should not have write access + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}). + SetHeader("Accept", lfs.MediaType). + AddBasicAuth("gitea-actions", task1Token) + MakeRequest(t, req, http.StatusUnauthorized) + + // set repo1&repo2 max permission to "write" so that the actions token can access code + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{ + "token_permission_mode": "restricted", + "override_owner_config": "true", + "enable_max_permissions": "true", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ + "token_permission_mode": "restricted", + "override_owner_config": "true", + "enable_max_permissions": "true", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // now task1 has write access to repo1, but still only read access to repo2 (different repo) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo1.Name), lfs.BatchRequest{Operation: "upload"}). + SetHeader("Accept", lfs.MediaType). + AddBasicAuth("gitea-actions", task1Token) + MakeRequest(t, req, http.StatusOK) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}). + SetHeader("Accept", lfs.MediaType). + AddBasicAuth("gitea-actions", task1Token) + MakeRequest(t, req, http.StatusUnauthorized) }) }) } @@ -362,114 +469,3 @@ func createActionTask(t *testing.T, repoID int64, isFork bool) *actions_model.Ac require.NoError(t, db.Insert(t.Context(), task)) return task } - -func TestActionsTokenPermissionsPersistenceWithWorkflow(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - // create repos - repo1 := createActionsTestRepo(t, token, "actions-permission-repo1", false) - repo2 := createActionsTestRepo(t, token, "actions-permission-repo2", true) - - // add repo2 to owner-level cross-repo access list - req := NewRequestWithValues(t, "POST", "/user/settings/actions/general", map[string]string{ - "cross_repo_add_target": "true", - "cross_repo_add_target_name": repo2.Name, - }) - session.MakeRequest(t, req, http.StatusOK) - - // create the runner for repo1 - runner1 := newMockRunner() - runner1.registerAsRepoRunner(t, user2.Name, repo1.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - // set repo1 actions token permission mode to "permissive" - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{ - "token_permission_mode": "permissive", - "override_owner_config": "true", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // set repo2 actions token permission mode to "restricted", and set max permissions - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ - "token_permission_mode": "restricted", - "override_owner_config": "true", - "enable_max_permissions": "true", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // create a workflow file with "permission" keyword for repo1 - wfTreePath := ".gitea/workflows/test_permissions.yml" - wfFileContent := `name: Test Permissions -on: - push: - paths: - - '.gitea/workflows/test_permissions.yml' - -jobs: - job-override: - runs-on: ubuntu-latest - permissions: - code: write - steps: - - run: echo "test perms" -` - opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, token, user2.Name, repo1.Name, wfTreePath, opts) - - task1 := runner1.fetchTask(t) - task1Token := task1.Secrets["GITEA_TOKEN"] - require.NotEmpty(t, task1Token) - - // should fail: target repo does not allow code access - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token) - MakeRequest(t, req, http.StatusNotFound) - - // set repo2 max permission to "read" so that the actions token can access code - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ - "token_permission_mode": "restricted", - "override_owner_config": "true", - "enable_max_permissions": "true", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "read", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // should succeed: target repo now allows code read access for this token - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token) - MakeRequest(t, req, http.StatusOK) - // but it should not have write access - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}). - SetHeader("Accept", lfs.MediaType). - AddBasicAuth("gitea-actions", task1Token) - MakeRequest(t, req, http.StatusUnauthorized) - - // set repo1&repo2 max permission to "write" so that the actions token can access code - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{ - "token_permission_mode": "restricted", - "override_owner_config": "true", - "enable_max_permissions": "true", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ - "token_permission_mode": "restricted", - "override_owner_config": "true", - "enable_max_permissions": "true", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // now task1 has write access to repo1, but still only read access to repo2 (different repo) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo1.Name), lfs.BatchRequest{Operation: "upload"}). - SetHeader("Accept", lfs.MediaType). - AddBasicAuth("gitea-actions", task1Token) - MakeRequest(t, req, http.StatusOK) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}). - SetHeader("Accept", lfs.MediaType). - AddBasicAuth("gitea-actions", task1Token) - MakeRequest(t, req, http.StatusUnauthorized) - }) -} diff --git a/tests/integration/actions_log_test.go b/tests/integration/actions_log_test.go index 51f705e96f0..e3110acb988 100644 --- a/tests/integration/actions_log_test.go +++ b/tests/integration/actions_log_test.go @@ -28,121 +28,13 @@ import ( func TestDownloadTaskLogs(t *testing.T) { now := time.Now() testCases := []struct { - treePath string - fileContent string - outcome []*mockTaskOutcome + name string + logState string + logStart time.Duration zstdEnabled bool }{ - { - treePath: ".gitea/workflows/download-task-logs-zstd.yml", - fileContent: `name: download-task-logs-zstd -on: - push: - paths: - - '.gitea/workflows/download-task-logs-zstd.yml' -jobs: - job1: - runs-on: ubuntu-latest - steps: - - run: echo job1 with zstd enabled - job2: - runs-on: ubuntu-latest - steps: - - run: echo job2 with zstd enabled -`, - outcome: []*mockTaskOutcome{ - { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(1 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(2 * time.Second)), - Content: "job1 zstd enabled", - }, - { - Time: timestamppb.New(now.Add(3 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(1 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(2 * time.Second)), - Content: "job2 zstd enabled", - }, - { - Time: timestamppb.New(now.Add(3 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - }, - zstdEnabled: true, - }, - { - treePath: ".gitea/workflows/download-task-logs-no-zstd.yml", - fileContent: `name: download-task-logs-no-zstd -on: - push: - paths: - - '.gitea/workflows/download-task-logs-no-zstd.yml' -jobs: - job1: - runs-on: ubuntu-latest - steps: - - run: echo job1 with zstd disabled - job2: - runs-on: ubuntu-latest - steps: - - run: echo job2 with zstd disabled -`, - outcome: []*mockTaskOutcome{ - { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job1 zstd disabled", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job2 zstd disabled", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - }, - zstdEnabled: false, - }, + {"download-task-logs-zstd", "enabled", time.Second, true}, + {"download-task-logs-no-zstd", "disabled", 4 * time.Second, false}, } onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -155,7 +47,8 @@ jobs: runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) for _, tc := range testCases { - t.Run("test "+tc.treePath, func(t *testing.T) { + treePath := ".gitea/workflows/" + tc.name + ".yml" + t.Run("test "+treePath, func(t *testing.T) { var resetFunc func() if tc.zstdEnabled { resetFunc = test.MockVariableValue(&setting.Actions.LogCompression, "zstd") @@ -166,11 +59,33 @@ jobs: } // create the workflow file - opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+tc.treePath, tc.fileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts) + opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+treePath, fmt.Sprintf(`name: %s +on: + push: + paths: + - '%s' +jobs: + job1: + runs-on: ubuntu-latest + steps: + - run: echo job1 with zstd %s + job2: + runs-on: ubuntu-latest + steps: + - run: echo job2 with zstd %s +`, tc.name, treePath, tc.logState, tc.logState)) + createWorkflowFile(t, token, user2.Name, repo.Name, treePath, opts) // fetch and execute tasks - for _, outcome := range tc.outcome { + for jobIndex := range 2 { + outcome := &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + {Time: timestamppb.New(now.Add(tc.logStart)), Content: " \U0001F433 docker create image"}, + {Time: timestamppb.New(now.Add(tc.logStart + time.Second)), Content: fmt.Sprintf("job%d zstd %s", jobIndex+1, tc.logState)}, + {Time: timestamppb.New(now.Add(tc.logStart + 2*time.Second)), Content: "\U0001F3C1 Job succeeded"}, + }, + } task := runner.fetchTask(t) runner.execTask(t, task, outcome) @@ -185,31 +100,22 @@ jobs: _, job, run := getTaskAndJobAndRunByTaskID(t, task.Id) // download task logs and check content - req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/logs", user2.Name, repo.Name, run.ID, job.ID)). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n") - assert.Len(t, logTextLines, len(outcome.logRows)) - for idx, lr := range outcome.logRows { - assert.Equal( - t, - fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), - logTextLines[idx], - ) - } - - // download task logs from API and check content - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, job.ID)). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - logTextLines = strings.Split(strings.TrimSpace(resp.Body.String()), "\n") - assert.Len(t, logTextLines, len(outcome.logRows)) - for idx, lr := range outcome.logRows { - assert.Equal( - t, - fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), - logTextLines[idx], - ) + for _, logURL := range []string{ + fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/logs", user2.Name, repo.Name, run.ID, job.ID), + // download task logs from API and check content + fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, job.ID), + } { + req := NewRequest(t, "GET", logURL).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n") + assert.Len(t, logTextLines, len(outcome.logRows)) + for idx, lr := range outcome.logRows { + assert.Equal( + t, + fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), + logTextLines[idx], + ) + } } } resetFunc() diff --git a/tests/integration/actions_rerun_test.go b/tests/integration/actions_rerun_test.go index 660a4f2f29c..467e7d46efd 100644 --- a/tests/integration/actions_rerun_test.go +++ b/tests/integration/actions_rerun_test.go @@ -111,18 +111,13 @@ jobs: req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.ID, job1.ID)) session.MakeRequest(t, req, http.StatusOK) // job2 needs job1, so rerunning job1 will also rerun job2 - // fetch and exec job1 - job1TaskR2 := runner.fetchTask(t) - assert.Equal(t, "3", job1TaskR2.Context.GetFields()["run_attempt"].GetStringValue()) - runner.execTask(t, job1TaskR2, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - // fetch and exec job2 - job2TaskR2 := runner.fetchTask(t) - assert.Equal(t, "3", job2TaskR2.Context.GetFields()["run_attempt"].GetStringValue()) - runner.execTask(t, job2TaskR2, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) + for range 2 { + task := runner.fetchTask(t) + assert.Equal(t, "3", task.Context.GetFields()["run_attempt"].GetStringValue()) + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + } assert.EqualValues(t, 3, getRunLatestAttemptNum(t, run.ID)) // RERUN-3: rerun job2 @@ -201,17 +196,13 @@ jobs: req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.ID)) session.MakeRequest(t, req, http.StatusOK) - // fetch and exec job1 - job1TaskR4 := runner.fetchTask(t) - assert.Equal(t, "5", job1TaskR4.Context.GetFields()["run_attempt"].GetStringValue()) - runner.execTask(t, job1TaskR4, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - job2TaskR4 := runner.fetchTask(t) - assert.Equal(t, "5", job2TaskR4.Context.GetFields()["run_attempt"].GetStringValue()) - runner.execTask(t, job2TaskR4, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) + for range 2 { + task := runner.fetchTask(t) + assert.Equal(t, "5", task.Context.GetFields()["run_attempt"].GetStringValue()) + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + } assert.EqualValues(t, 5, getRunLatestAttemptNum(t, run.ID)) }) }) @@ -282,68 +273,46 @@ jobs: legacyRun.Created = started - 5 legacyRun.Updated = stopped - legacyJob1 := &actions_model.ActionRunJob{ - RunID: legacyRun.ID, - RepoID: repo.ID, - OwnerID: repo.OwnerID, - CommitSHA: legacyRun.CommitSHA, - Name: payloads["job1"].name, - Attempt: 1, - WorkflowPayload: payloads["job1"].payload, - JobID: "job1", - Needs: payloads["job1"].needs, - RunsOn: payloads["job1"].runsOn, - Status: actions_model.StatusSuccess, - RunAttemptID: 0, - AttemptJobID: 0, - Started: started, - Stopped: stopped, - IsForkPullRequest: false, - } - legacyJob2 := &actions_model.ActionRunJob{ - RunID: legacyRun.ID, - RepoID: repo.ID, - OwnerID: repo.OwnerID, - CommitSHA: legacyRun.CommitSHA, - Name: payloads["job2"].name, - Attempt: 1, - WorkflowPayload: payloads["job2"].payload, - JobID: "job2", - Needs: payloads["job2"].needs, - RunsOn: payloads["job2"].runsOn, - Status: actions_model.StatusSuccess, - RunAttemptID: 0, - AttemptJobID: 0, - Started: started, - Stopped: stopped, - IsForkPullRequest: false, + legacyJobs := [2]*actions_model.ActionRunJob{} + for index, jobID := range []string{"job1", "job2"} { + legacyJobs[index] = &actions_model.ActionRunJob{ + RunID: legacyRun.ID, + RepoID: repo.ID, + OwnerID: repo.OwnerID, + CommitSHA: legacyRun.CommitSHA, + Name: payloads[jobID].name, + Attempt: 1, + WorkflowPayload: payloads[jobID].payload, + JobID: jobID, + Needs: payloads[jobID].needs, + RunsOn: payloads[jobID].runsOn, + Status: actions_model.StatusSuccess, + RunAttemptID: 0, + AttemptJobID: 0, + Started: started, + Stopped: stopped, + IsForkPullRequest: false, + } } + legacyJob1, legacyJob2 := legacyJobs[0], legacyJobs[1] require.NoError(t, db.Insert(t.Context(), legacyJob1, legacyJob2)) - legacyTask1 := &actions_model.ActionTask{ - JobID: legacyJob1.ID, - Attempt: 1, - Status: actions_model.StatusSuccess, - Started: started, - Stopped: stopped, - RepoID: repo.ID, - OwnerID: repo.OwnerID, - CommitSHA: legacyRun.CommitSHA, - IsForkPullRequest: false, + legacyTasks := [2]*actions_model.ActionTask{} + for index, legacyJob := range legacyJobs { + legacyTasks[index] = &actions_model.ActionTask{ + JobID: legacyJob.ID, + Attempt: 1, + Status: actions_model.StatusSuccess, + Started: started, + Stopped: stopped, + RepoID: repo.ID, + OwnerID: repo.OwnerID, + CommitSHA: legacyRun.CommitSHA, + IsForkPullRequest: false, + } + legacyTasks[index].GenerateAndFillToken() } - legacyTask1.GenerateAndFillToken() - legacyTask2 := &actions_model.ActionTask{ - JobID: legacyJob2.ID, - Attempt: 1, - Status: actions_model.StatusSuccess, - Started: started, - Stopped: stopped, - RepoID: repo.ID, - OwnerID: repo.OwnerID, - CommitSHA: legacyRun.CommitSHA, - IsForkPullRequest: false, - } - legacyTask2.GenerateAndFillToken() + legacyTask1, legacyTask2 := legacyTasks[0], legacyTasks[1] require.NoError(t, db.Insert(t.Context(), legacyTask1, legacyTask2)) legacyJob1.TaskID = legacyTask1.ID diff --git a/tests/integration/actions_reusable_workflow_test.go b/tests/integration/actions_reusable_workflow_test.go index db13d3b3c3f..0f4b8382e6d 100644 --- a/tests/integration/actions_reusable_workflow_test.go +++ b/tests/integration/actions_reusable_workflow_test.go @@ -168,6 +168,22 @@ jobs: r2Job1AttemptJobID int64 ) + completeCallerJob3 := func(t *testing.T, expectedOutput string) { + callerJob3Task := defaultRunner.fetchTask(t) // for caller_job3 + _, callerJob3, _ := getTaskAndJobAndRunByTaskID(t, callerJob3Task.Id) + assert.Equal(t, "caller_job3", callerJob3.JobID) + if assert.Len(t, callerJob3Task.Needs, 1) { + assert.Contains(t, callerJob3Task.Needs, "caller_job2") + assert.Equal(t, runnerv1.Result_RESULT_SUCCESS, callerJob3Task.Needs["caller_job2"].Result) + if assert.Len(t, callerJob3Task.Needs["caller_job2"].Outputs, 1) { + assert.Equal(t, expectedOutput, callerJob3Task.Needs["caller_job2"].Outputs["r1_out"]) + } + } + defaultRunner.execTask(t, callerJob3Task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + } + t.Run("Check initialized jobs", func(t *testing.T) { // run assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID})) @@ -279,19 +295,7 @@ jobs: callerJob2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: callerJob2ID}) assert.Equal(t, actions_model.StatusSuccess, callerJob2.Status) - callerJob3Task := defaultRunner.fetchTask(t) // for caller_job3 - _, callerJob3, _ := getTaskAndJobAndRunByTaskID(t, callerJob3Task.Id) - assert.Equal(t, "caller_job3", callerJob3.JobID) - if assert.Len(t, callerJob3Task.Needs, 1) { - assert.Contains(t, callerJob3Task.Needs, "caller_job2") - assert.Equal(t, runnerv1.Result_RESULT_SUCCESS, callerJob3Task.Needs["caller_job2"].Result) - if assert.Len(t, callerJob3Task.Needs["caller_job2"].Outputs, 1) { - assert.Equal(t, "r1j2_out_data", callerJob3Task.Needs["caller_job2"].Outputs["r1_out"]) - } - } - defaultRunner.execTask(t, callerJob3Task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) + completeCallerJob3(t, "r1j2_out_data") callerRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: runID}) assert.Equal(t, actions_model.StatusSuccess, callerRun.Status) }) @@ -349,19 +353,7 @@ jobs: callerJob2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: callerJob2.ID}) assert.Equal(t, actions_model.StatusSuccess, callerJob2.Status) - callerJob3Task := defaultRunner.fetchTask(t) - _, callerJob3, _ = getTaskAndJobAndRunByTaskID(t, callerJob3Task.Id) - assert.Equal(t, "caller_job3", callerJob3.JobID) - if assert.Len(t, callerJob3Task.Needs, 1) { - assert.Contains(t, callerJob3Task.Needs, "caller_job2") - assert.Equal(t, runnerv1.Result_RESULT_SUCCESS, callerJob3Task.Needs["caller_job2"].Result) - if assert.Len(t, callerJob3Task.Needs["caller_job2"].Outputs, 1) { - assert.Equal(t, "r1j2_out_data_updated", callerJob3Task.Needs["caller_job2"].Outputs["r1_out"]) - } - } - defaultRunner.execTask(t, callerJob3Task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) + completeCallerJob3(t, "r1j2_out_data_updated") attempt2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunAttempt{RunID: runID, Attempt: 2}) assert.Equal(t, actions_model.StatusSuccess, attempt2.Status) run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: runID}) diff --git a/tests/integration/actions_runner_modify_test.go b/tests/integration/actions_runner_modify_test.go index e3392ee57c4..8e4ae3004b4 100644 --- a/tests/integration/actions_runner_modify_test.go +++ b/tests/integration/actions_runner_modify_test.go @@ -57,26 +57,16 @@ func TestActionsRunnerModify(t *testing.T) { sess.MakeRequest(t, req, expectedStatus) } - doDelete := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) { - req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id)) - sess.MakeRequest(t, req, expectedStatus) - } - - doDisable := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) { - req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/update-runner?disabled=true", baseURL, id)) - sess.MakeRequest(t, req, expectedStatus) - } - - doEnable := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) { - req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/update-runner?disabled=false", baseURL, id)) + doAction := func(t *testing.T, sess *TestSession, baseURL string, id int64, action string, expectedStatus int) { + req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/%s", baseURL, id, action)) sess.MakeRequest(t, req, expectedStatus) } assertDenied := func(t *testing.T, sess *TestSession, baseURL string, id int64) { doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusNotFound) - doDisable(t, sess, baseURL, id, http.StatusNotFound) - doEnable(t, sess, baseURL, id, http.StatusNotFound) - doDelete(t, sess, baseURL, id, http.StatusNotFound) + doAction(t, sess, baseURL, id, "update-runner?disabled=true", http.StatusNotFound) + doAction(t, sess, baseURL, id, "update-runner?disabled=false", http.StatusNotFound) + doAction(t, sess, baseURL, id, "delete", http.StatusNotFound) v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) assert.Empty(t, v.Description) assert.False(t, v.IsDisabled) @@ -86,13 +76,13 @@ func TestActionsRunnerModify(t *testing.T) { doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusSeeOther) v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) assert.Equal(t, "ChangedDescription", v.Description) - doDisable(t, sess, baseURL, id, http.StatusOK) + doAction(t, sess, baseURL, id, "update-runner?disabled=true", http.StatusOK) v = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) assert.True(t, v.IsDisabled) - doEnable(t, sess, baseURL, id, http.StatusOK) + doAction(t, sess, baseURL, id, "update-runner?disabled=false", http.StatusOK) v = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) assert.False(t, v.IsDisabled) - doDelete(t, sess, baseURL, id, http.StatusOK) + doAction(t, sess, baseURL, id, "delete", http.StatusOK) unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: id}) } diff --git a/tests/integration/actions_scoped_workflow_test.go b/tests/integration/actions_scoped_workflow_test.go index 51b273a2f95..aa34e728e44 100644 --- a/tests/integration/actions_scoped_workflow_test.go +++ b/tests/integration/actions_scoped_workflow_test.go @@ -239,6 +239,48 @@ jobs: "both the web form and the API created a scoped dispatch run") }) + // protectAndOpenPR protects consumer's default branch and opens a PR on `branch`, returning a merge-request builder. + // When statusCheckEnabled it also configures "ci/manual" as the only CONFIGURED required context and satisfies it, + // so the scoped check is the only thing that can gate the merge; otherwise the rule's own status check stays off. + protectAndOpenPR := func(t *testing.T, consumer *repo_model.Repository, branch, filename string, statusCheckEnabled bool) (api.PullRequest, func() *RequestWrapper) { + pbValues := map[string]string{ + "rule_name": consumer.DefaultBranch, + "enable_push": "true", + "block_admin_merge_override": "true", // otherwise the repo owner bypasses the status check + } + if statusCheckEnabled { + pbValues["enable_status_check"] = "true" + pbValues["status_check_contexts"] = "ci/manual" + } + user2Session.MakeRequest(t, NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", consumer.OwnerName, consumer.Name), pbValues), http.StatusSeeOther) + + prFile := &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: consumer.DefaultBranch, NewBranchName: branch, Message: "pr change", + Author: api.Identity{Name: user2.Name, Email: user2.Email}, + Committer: api.Identity{Name: user2.Name, Email: user2.Email}, + Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()}, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("pr change")), + } + createWorkflowFile(t, user2Token, consumer.OwnerName, consumer.Name, filename, prFile) + apiCtx := NewAPITestContext(t, user2.Name, consumer.Name, auth_model.AccessTokenScopeWriteRepository) + pr, err := doAPICreatePullRequest(apiCtx, consumer.OwnerName, consumer.Name, consumer.DefaultBranch, branch)(t) + require.NoError(t, err) + + if statusCheckEnabled { + // satisfy the configured "ci/manual" check so only the scoped check can gate the merge + manualStatus := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", consumer.OwnerName, consumer.Name, pr.Head.Sha), + api.CreateStatusOption{State: commitstatus.CommitStatusSuccess, Context: "ci/manual", TargetURL: "http://test.ci/"}).AddTokenAuth(user2Token) + user2Session.MakeRequest(t, manualStatus, http.StatusCreated) + } + + return pr, func() *RequestWrapper { + return NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", consumer.OwnerName, consumer.Name, pr.Index), + &forms.MergePullRequestForm{Do: string(repo_model.MergeStyleMerge), MergeMessageField: "merge"}).AddTokenAuth(user2Token) + } + } + t.Run("Required scoped check gates the PR merge", func(t *testing.T) { // A required scoped workflow's check gates PR merges on a protected branch and cannot be bypassed, // whether or not the branch enables its own status check. The scoped check is added to the required set dynamically. @@ -246,54 +288,12 @@ jobs: createRepoWorkflowFile(t, user2, user2Token, source, ".gitea/scoped_workflows/pr.yaml", scopedPRWorkflow) registerUserScopedSource(t, source, "pr.yaml") // required - // protectAndOpenPR protects consumer's default branch and opens a PR on `branch`, returning a merge-request builder. - // When statusCheckEnabled it also configures "ci/manual" as the only CONFIGURED required context and satisfies it, - // so the scoped check is the only thing that can gate the merge; otherwise the rule's own status check stays off. - protectAndOpenPR := func(t *testing.T, consumer *repo_model.Repository, branch string, statusCheckEnabled bool) func() *RequestWrapper { - pbValues := map[string]string{ - "rule_name": consumer.DefaultBranch, - "enable_push": "true", - "block_admin_merge_override": "true", // otherwise the repo owner bypasses the status check - } - if statusCheckEnabled { - pbValues["enable_status_check"] = "true" - pbValues["status_check_contexts"] = "ci/manual" - } - user2Session.MakeRequest(t, NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", consumer.OwnerName, consumer.Name), pbValues), http.StatusSeeOther) - - prFile := &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - BranchName: consumer.DefaultBranch, NewBranchName: branch, Message: "pr change", - Author: api.Identity{Name: user2.Name, Email: user2.Email}, - Committer: api.Identity{Name: user2.Name, Email: user2.Email}, - Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()}, - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("pr change")), - } - createWorkflowFile(t, user2Token, consumer.OwnerName, consumer.Name, "pr-change.txt", prFile) - apiCtx := NewAPITestContext(t, user2.Name, consumer.Name, auth_model.AccessTokenScopeWriteRepository) - pr, err := doAPICreatePullRequest(apiCtx, consumer.OwnerName, consumer.Name, consumer.DefaultBranch, branch)(t) - require.NoError(t, err) - - if statusCheckEnabled { - // satisfy the configured "ci/manual" check so only the scoped check can gate the merge - manualStatus := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", consumer.OwnerName, consumer.Name, pr.Head.Sha), - api.CreateStatusOption{State: commitstatus.CommitStatusSuccess, Context: "ci/manual", TargetURL: "http://test.ci/"}).AddTokenAuth(user2Token) - user2Session.MakeRequest(t, manualStatus, http.StatusCreated) - } - - return func() *RequestWrapper { - return NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", consumer.OwnerName, consumer.Name, pr.Index), - &forms.MergePullRequestForm{Do: string(repo_model.MergeStyleMerge), MergeMessageField: "merge"}).AddTokenAuth(user2Token) - } - } - t.Run("pending blocks, success allows", func(t *testing.T) { consumer := createTestRepo(t, "sw-gate-consumer", false) runner := newMockRunner() runner.registerAsRepoRunner(t, consumer.OwnerName, consumer.Name, "sw-gate-runner", []string{"ubuntu-latest"}, false) - mergeReq := protectAndOpenPR(t, consumer, "gate-pr", true) + _, mergeReq := protectAndOpenPR(t, consumer, "gate-pr", "pr-change.txt", true) run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: consumer.ID, IsScopedRun: true}) assert.Equal(t, source.ID, run.WorkflowRepoID) @@ -314,7 +314,7 @@ jobs: consumer := createTestRepo(t, "sw-noact-consumer", false) require.NoError(t, repo_service.UpdateRepositoryUnits(t.Context(), consumer, nil, []unit_model.Type{unit_model.TypeActions})) - mergeReq := protectAndOpenPR(t, consumer, "noact-pr", true) + _, mergeReq := protectAndOpenPR(t, consumer, "noact-pr", "pr-change.txt", true) assert.Equal(t, 0, unittest.GetCount(t, &actions_model.ActionRun{RepoID: consumer.ID, IsScopedRun: true}), "Actions disabled, so no scoped run is created") @@ -329,7 +329,7 @@ jobs: runner := newMockRunner() runner.registerAsRepoRunner(t, consumer.OwnerName, consumer.Name, "sw-nocheck-runner", []string{"ubuntu-latest"}, false) - mergeReq := protectAndOpenPR(t, consumer, "nocheck-pr", false) + _, mergeReq := protectAndOpenPR(t, consumer, "nocheck-pr", "pr-change.txt", false) unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: consumer.ID, IsScopedRun: true}) // pending scoped check blocks the merge despite the branch's own status check being off @@ -365,26 +365,8 @@ jobs: consumer := createTestRepo(t, "sw-filtered-consumer", false) // Protect the default branch (its own status check stays off, so only the required scoped check gates the merge). - user2Session.MakeRequest(t, NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", consumer.OwnerName, consumer.Name), map[string]string{ - "rule_name": consumer.DefaultBranch, - "enable_push": "true", - "block_admin_merge_override": "true", // otherwise the repo owner bypasses the status check - }), http.StatusSeeOther) - // Open a PR that changes a file NOT matching the workflow's `paths: [src/**]`, so it is filtered out. - prFile := &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - BranchName: consumer.DefaultBranch, NewBranchName: "filtered-pr", Message: "pr change", - Author: api.Identity{Name: user2.Name, Email: user2.Email}, - Committer: api.Identity{Name: user2.Name, Email: user2.Email}, - Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()}, - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("pr change")), - } - createWorkflowFile(t, user2Token, consumer.OwnerName, consumer.Name, "docs.txt", prFile) - apiCtx := NewAPITestContext(t, user2.Name, consumer.Name, auth_model.AccessTokenScopeWriteRepository) - pr, err := doAPICreatePullRequest(apiCtx, consumer.OwnerName, consumer.Name, consumer.DefaultBranch, "filtered-pr")(t) - require.NoError(t, err) + pr, mergeReq := protectAndOpenPR(t, consumer, "filtered-pr", "docs.txt", false) // Filtered: no scoped run is created, but a skipped commit status is posted on the PR head. assert.Equal(t, 0, unittest.GetCount(t, &actions_model.ActionRun{RepoID: consumer.ID, IsScopedRun: true}), "filtered scoped workflow creates no run") @@ -392,9 +374,7 @@ jobs: // The skipped (success) status satisfies the required scoped check (prefixed with the source repo), so the merge is allowed. assert.NoError(t, queue.GetManager().FlushAll(t.Context(), 5*time.Second)) - mergeReq := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", consumer.OwnerName, consumer.Name, pr.Index), - &forms.MergePullRequestForm{Do: string(repo_model.MergeStyleMerge), MergeMessageField: "merge"}).AddTokenAuth(user2Token) - user2Session.MakeRequest(t, mergeReq, http.StatusOK) + user2Session.MakeRequest(t, mergeReq(), http.StatusOK) }) t.Run("Settings page required patterns", func(t *testing.T) { diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 178163cd508..e39bead7c57 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -423,31 +423,32 @@ jobs: }) } -func TestCreateDeleteRefEvent(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) +func TestActionsTriggerEvents(t *testing.T) { + onGiteaRun(t, func(t *testing.T, _u *url.URL) { + t.Run("CreateDeleteRefEvent", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "create-delete-ref-event", - Description: "test create delete ref ci event", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "create-delete-ref-event", + Description: "test create delete ref ci event", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/createdelete.yml", - ContentReader: strings.NewReader(`name: test + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/createdelete.yml", + ContentReader: strings.NewReader(`name: test on: [create,delete] jobs: @@ -456,87 +457,572 @@ jobs: steps: - run: echo helloworld `), + }, }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) - // Get the commit ID of the default branch - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) + // Get the commit ID of the default branch + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) - // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-create-branch") - assert.NoError(t, err) - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "create", - Ref: "refs/heads/test-create-branch", - WorkflowID: "createdelete.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) + // create a branch + err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-create-branch") + assert.NoError(t, err) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "create", + Ref: "refs/heads/test-create-branch", + WorkflowID: "createdelete.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) - // create a tag - err = release_service.CreateNewTag(t.Context(), user2, repo, branch.CommitID, "test-create-tag", "test create tag event") - assert.NoError(t, err) - run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "create", - Ref: "refs/tags/test-create-tag", - WorkflowID: "createdelete.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) + // create a tag + err = release_service.CreateNewTag(t.Context(), user2, repo, branch.CommitID, "test-create-tag", "test create tag event") + assert.NoError(t, err) + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "create", + Ref: "refs/tags/test-create-tag", + WorkflowID: "createdelete.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) - // delete the branch - err = repo_service.DeleteBranch(t.Context(), user2, repo, gitRepo, "test-create-branch") - assert.NoError(t, err) - run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "delete", - Ref: "refs/heads/main", - WorkflowID: "createdelete.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) + // delete the branch + err = repo_service.DeleteBranch(t.Context(), user2, repo, gitRepo, "test-create-branch") + assert.NoError(t, err) + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "delete", + Ref: "refs/heads/main", + WorkflowID: "createdelete.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) - // delete the tag - tag, err := repo_model.GetRelease(t.Context(), repo.ID, "test-create-tag") - assert.NoError(t, err) - err = release_service.DeleteReleaseByID(t.Context(), repo, tag, user2, true) - assert.NoError(t, err) - run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "delete", - Ref: "refs/heads/main", - WorkflowID: "createdelete.yml", - CommitSHA: branch.CommitID, + // delete the tag + tag, err := repo_model.GetRelease(t.Context(), repo.ID, "test-create-tag") + assert.NoError(t, err) + err = release_service.DeleteReleaseByID(t.Context(), repo, tag, user2, true) + assert.NoError(t, err) + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "delete", + Ref: "refs/heads/main", + WorkflowID: "createdelete.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + }) + + t.Run("WorkflowDispatchPublicApi", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-event", + Description: "test workflow-dispatch ci event", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + // Get the commit ID of the default branch + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + values := url.Values{} + values.Set("ref", "main") + req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + + // Now trigger with rundetails + values.Set("return_run_details", "true") + + req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + runDetails := &api.RunDetails{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(runDetails)) + assert.NotEqual(t, 0, runDetails.WorkflowRunID) + + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + ID: runDetails.WorkflowRunID, + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + + _, _, err = gitcmd.NewCommand("update-ref").AddDynamicArguments("refs/tags/refs/heads/spoof", branch.CommitID).WithRepo(repo).RunStdString(t.Context()) + require.NoError(t, err) + values.Set("ref", "spoof") + req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("WorkflowDispatchPublicApiRequiresWorkflowDispatchTrigger", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-requires-trigger", + Description: "test workflow dispatch requires workflow_dispatch", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + require.NoError(t, err) + require.NotNil(t, repo) + + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/push-only.yml", + ContentReader: strings.NewReader(` +on: + push: +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + require.NoError(t, err) + require.NotNil(t, addWorkflowToBaseResp) + + values := url.Values{} + values.Set("ref", "main") + req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/push-only.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusUnprocessableEntity) + apiError := DecodeJSON(t, resp, &api.APIError{}) + assert.Contains(t, apiError.Message, "has no workflow_dispatch event trigger") + + unittest.AssertNotExistsBean(t, &actions_model.ActionRun{ + RepoID: repo.ID, + Event: "workflow_dispatch", + WorkflowID: "push-only.yml", + }) + }) + + t.Run("WorkflowApi", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-api", + Description: "test workflow apis", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + workflows := &api.ActionWorkflowResponse{} + json.NewDecoder(resp.Body).Decode(workflows) + assert.Empty(t, workflows.Workflows) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + json.NewDecoder(resp.Body).Decode(workflows) + assert.Len(t, workflows.Workflows, 1) + assert.Equal(t, "dispatch.yml", workflows.Workflows[0].Name) + assert.Equal(t, ".gitea/workflows/dispatch.yml", workflows.Workflows[0].Path) + assert.Equal(t, "active", workflows.Workflows[0].State) + + checkWorkflow := func(workflowURL, state string) { + req := NewRequest(t, "GET", workflowURL). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + workflow := &api.ActionWorkflow{} + json.NewDecoder(resp.Body).Decode(workflow) + assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) + assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) + assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) + assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) + assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) + assert.Equal(t, state, workflow.State) + } + + // Use a hardcoded api path + checkWorkflow(fmt.Sprintf("/api/v1/repos/%s/actions/workflows/%s", repo.FullName(), workflows.Workflows[0].ID), workflows.Workflows[0].State) + + // Use the provided url instead of the hardcoded one + checkWorkflow(workflows.Workflows[0].URL, workflows.Workflows[0].State) + + // Disable the workflow + req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/disable"). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + // Use the provided url instead of the hardcoded one + checkWorkflow(workflows.Workflows[0].URL, "disabled_manually") + + inputs := &api.CreateActionWorkflowDispatch{ + Ref: "main", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + // Since the workflow is disabled, so the response code is 403 forbidden + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusForbidden) + + // Enable the workflow again + req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/enable"). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + // Use the provided url instead of the hardcoded one + checkWorkflow(workflows.Workflows[0].URL, workflows.Workflows[0].State) + + checkWorkflow(workflows.Workflows[0].URL, workflows.Workflows[0].State) + + // Get the commit ID of the default branch + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + inputs = &api.CreateActionWorkflowDispatch{ + Ref: "main", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + dispatchPayload := &api.WorkflowDispatchPayload{} + err = json.Unmarshal([]byte(run.EventPayload), dispatchPayload) + assert.NoError(t, err) + assert.Contains(t, dispatchPayload.Inputs, "myinput") + assert.Contains(t, dispatchPayload.Inputs, "myinput2") + assert.Contains(t, dispatchPayload.Inputs, "myinput3") + assert.Equal(t, "val0", dispatchPayload.Inputs["myinput"]) + assert.Equal(t, "def2", dispatchPayload.Inputs["myinput2"]) + assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) + }) + + t.Run("ActionRunNameWithContextVariables", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "action-run-name-with-variables", + Description: "test action run name", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/runname.yml", + ContentReader: strings.NewReader(`name: test +on: + [create,delete] +run-name: ${{ gitea.actor }} is running this workflow +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow with run-name", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + + // Get the commit ID of the default branch + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + + // create a branch + err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name-with-variables") + assert.NoError(t, err) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: user2.LoginName + " is running this workflow", + RepoID: repo.ID, + Event: "create", + Ref: "refs/heads/test-action-run-name-with-variables", + WorkflowID: "runname.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + }) + + t.Run("ActionRunName", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "action-run-name", + Description: "test action run-name", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/runname.yml", + ContentReader: strings.NewReader(`name: test +on: + [create,delete] +run-name: run name without variables +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow with run name", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + + // Get the commit ID of the default branch + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + + // create a branch + err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name") + assert.NoError(t, err) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "run name without variables", + RepoID: repo.ID, + Event: "create", + Ref: "refs/heads/test-action-run-name", + WorkflowID: "runname.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) }) - assert.NotNil(t, run) }) } @@ -895,184 +1381,6 @@ jobs: }) } -func TestWorkflowDispatchPublicApi(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "workflow-dispatch-event", - Description: "test workflow-dispatch ci event", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(` -on: - workflow_dispatch -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - // Get the commit ID of the default branch - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - values := url.Values{} - values.Set("ref", "main") - req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "workflow_dispatch", - Ref: "refs/heads/main", - WorkflowID: "dispatch.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - - // Now trigger with rundetails - values.Set("return_run_details", "true") - - req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - runDetails := &api.RunDetails{} - require.NoError(t, json.NewDecoder(resp.Body).Decode(runDetails)) - assert.NotEqual(t, 0, runDetails.WorkflowRunID) - - run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - ID: runDetails.WorkflowRunID, - Title: "add workflow", - RepoID: repo.ID, - Event: "workflow_dispatch", - Ref: "refs/heads/main", - WorkflowID: "dispatch.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - - _, _, err = gitcmd.NewCommand("update-ref").AddDynamicArguments("refs/tags/refs/heads/spoof", branch.CommitID).WithRepo(repo).RunStdString(t.Context()) - require.NoError(t, err) - values.Set("ref", "spoof") - req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - }) -} - -func TestWorkflowDispatchPublicApiRequiresWorkflowDispatchTrigger(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "workflow-dispatch-requires-trigger", - Description: "test workflow dispatch requires workflow_dispatch", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - require.NoError(t, err) - require.NotNil(t, repo) - - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/push-only.yml", - ContentReader: strings.NewReader(` -on: - push: -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - require.NoError(t, err) - require.NotNil(t, addWorkflowToBaseResp) - - values := url.Values{} - values.Set("ref", "main") - req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/push-only.yml/dispatches", repo.FullName()), values). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusUnprocessableEntity) - apiError := DecodeJSON(t, resp, &api.APIError{}) - assert.Contains(t, apiError.Message, "has no workflow_dispatch event trigger") - - unittest.AssertNotExistsBean(t, &actions_model.ActionRun{ - RepoID: repo.ID, - Event: "workflow_dispatch", - WorkflowID: "push-only.yml", - }) - }) -} - func TestWorkflowDispatchPublicApiWithInputs(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -1164,92 +1472,6 @@ jobs: }) } -func TestWorkflowDispatchPublicApiJSON(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "workflow-dispatch-event", - Description: "test workflow-dispatch ci event", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(` -on: - workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - // Get the commit ID of the default branch - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - inputs := &api.CreateActionWorkflowDispatch{ - Ref: "main", - Inputs: map[string]string{ - "myinput": "val0", - "myinput3": "true", - }, - } - - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "workflow_dispatch", - Ref: "refs/heads/main", - WorkflowID: "dispatch.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - }) -} - func TestWorkflowDispatchPublicApiWithInputsJSON(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -1475,203 +1697,6 @@ jobs: }) } -func TestWorkflowApi(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "workflow-api", - Description: "test workflow apis", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - workflows := &api.ActionWorkflowResponse{} - json.NewDecoder(resp.Body).Decode(workflows) - assert.Empty(t, workflows.Workflows) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(` -on: - workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - json.NewDecoder(resp.Body).Decode(workflows) - assert.Len(t, workflows.Workflows, 1) - assert.Equal(t, "dispatch.yml", workflows.Workflows[0].Name) - assert.Equal(t, ".gitea/workflows/dispatch.yml", workflows.Workflows[0].Path) - assert.Equal(t, ".gitea/workflows/dispatch.yml", workflows.Workflows[0].Path) - assert.Equal(t, "active", workflows.Workflows[0].State) - - // Use a hardcoded api path - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/%s", repo.FullName(), workflows.Workflows[0].ID)). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow := &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, workflows.Workflows[0].State, workflow.State) - - // Use the provided url instead of the hardcoded one - req = NewRequest(t, "GET", workflows.Workflows[0].URL). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow = &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, workflows.Workflows[0].State, workflow.State) - - // Disable the workflow - req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/disable"). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - // Use the provided url instead of the hardcoded one - req = NewRequest(t, "GET", workflows.Workflows[0].URL). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow = &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, "disabled_manually", workflow.State) - - inputs := &api.CreateActionWorkflowDispatch{ - Ref: "main", - Inputs: map[string]string{ - "myinput": "val0", - "myinput3": "true", - }, - } - // Since the workflow is disabled, so the response code is 403 forbidden - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusForbidden) - - // Enable the workflow again - req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/enable"). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - // Use the provided url instead of the hardcoded one - req = NewRequest(t, "GET", workflows.Workflows[0].URL). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow = &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, workflows.Workflows[0].State, workflow.State) - - req = NewRequest(t, "GET", workflows.Workflows[0].URL). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow = &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, workflows.Workflows[0].State, workflow.State) - - // Get the commit ID of the default branch - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - inputs = &api.CreateActionWorkflowDispatch{ - Ref: "main", - Inputs: map[string]string{ - "myinput": "val0", - "myinput3": "true", - }, - } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "workflow_dispatch", - Ref: "refs/heads/main", - WorkflowID: "dispatch.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - dispatchPayload := &api.WorkflowDispatchPayload{} - err = json.Unmarshal([]byte(run.EventPayload), dispatchPayload) - assert.NoError(t, err) - assert.Contains(t, dispatchPayload.Inputs, "myinput") - assert.Contains(t, dispatchPayload.Inputs, "myinput2") - assert.Contains(t, dispatchPayload.Inputs, "myinput3") - assert.Equal(t, "val0", dispatchPayload.Inputs["myinput"]) - assert.Equal(t, "def2", dispatchPayload.Inputs["myinput2"]) - assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) - }) -} - func TestClosePullRequestWithPath(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { // user2 is the owner of the base repo @@ -1749,162 +1774,6 @@ jobs: }) } -func TestActionRunNameWithContextVariables(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "action-run-name-with-variables", - Description: "test action run name", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/runname.yml", - ContentReader: strings.NewReader(`name: test -on: - [create,delete] -run-name: ${{ gitea.actor }} is running this workflow -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow with run-name", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - - // Get the commit ID of the default branch - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - - // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name-with-variables") - assert.NoError(t, err) - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: user2.LoginName + " is running this workflow", - RepoID: repo.ID, - Event: "create", - Ref: "refs/heads/test-action-run-name-with-variables", - WorkflowID: "runname.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - }) -} - -func TestActionRunName(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "action-run-name", - Description: "test action run-name", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/runname.yml", - ContentReader: strings.NewReader(`name: test -on: - [create,delete] -run-name: run name without variables -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow with run name", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - - // Get the commit ID of the default branch - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - - // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name") - assert.NoError(t, err) - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "run name without variables", - RepoID: repo.ID, - Event: "create", - Ref: "refs/heads/test-action-run-name", - WorkflowID: "runname.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - }) -} - func TestPullRequestWithPathsRebase(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/actions_variables_test.go b/tests/integration/actions_variables_test.go index 25dd7321c1e..0d25512c459 100644 --- a/tests/integration/actions_variables_test.go +++ b/tests/integration/actions_variables_test.go @@ -76,57 +76,30 @@ func TestActionsVariables(t *testing.T) { unittest.AssertNotExistsBean(t, &actions_model.ActionVariable{ID: id}) } - t.Run("UpdateUserVar", func(t *testing.T) { - theVar := user2Var - t.Run("FromOrg", func(t *testing.T) { - assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) + originURLs := map[string]string{ + "User": userWebURL, + "Org": orgWebURL, + "Repo": repoWebURL, + "Admin": adminWebURL, + } + for _, variable := range []struct { + name string + id int64 + origins []string + }{ + {"User", user2Var.ID, []string{"Org", "Repo", "Admin"}}, + {"Org", org3Var.ID, []string{"Repo", "User", "Admin"}}, + {"Repo", repo1Var.ID, []string{"Org", "User", "Admin"}}, + {"Global", globalVar.ID, []string{"Org", "User", "Repo"}}, + } { + t.Run("Update"+variable.name+"Var", func(t *testing.T) { + for _, origin := range variable.origins { + t.Run("From"+origin, func(t *testing.T) { + assertDenied(t, sessionAdmin, originURLs[origin], variable.id) + }) + } }) - t.Run("FromRepo", func(t *testing.T) { - assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) - }) - t.Run("FromAdmin", func(t *testing.T) { - assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) - }) - }) - - t.Run("UpdateOrgVar", func(t *testing.T) { - theVar := org3Var - t.Run("FromRepo", func(t *testing.T) { - assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) - }) - t.Run("FromUser", func(t *testing.T) { - assertDenied(t, sessionAdmin, userWebURL, theVar.ID) - }) - t.Run("FromAdmin", func(t *testing.T) { - assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) - }) - }) - - t.Run("UpdateRepoVar", func(t *testing.T) { - theVar := repo1Var - t.Run("FromOrg", func(t *testing.T) { - assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) - }) - t.Run("FromUser", func(t *testing.T) { - assertDenied(t, sessionAdmin, userWebURL, theVar.ID) - }) - t.Run("FromAdmin", func(t *testing.T) { - assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) - }) - }) - - t.Run("UpdateGlobalVar", func(t *testing.T) { - theVar := globalVar - t.Run("FromOrg", func(t *testing.T) { - assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) - }) - t.Run("FromUser", func(t *testing.T) { - assertDenied(t, sessionAdmin, userWebURL, theVar.ID) - }) - t.Run("FromRepo", func(t *testing.T) { - assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) - }) - }) + } t.Run("UpdateSuccess", func(t *testing.T) { t.Run("User", func(t *testing.T) { diff --git a/tests/integration/admin_audit_test.go b/tests/integration/admin_audit_test.go index b091456090b..a84f52b2c3e 100644 --- a/tests/integration/admin_audit_test.go +++ b/tests/integration/admin_audit_test.go @@ -23,71 +23,70 @@ import ( "github.com/stretchr/testify/require" ) -// Events an admin causes while impersonating must stay traceable to the admin, -// otherwise anything done in an impersonated session is pinned on the victim. -func TestAdminAuditLogImpersonation(t *testing.T) { +func TestAdminAuditLogAttribution(t *testing.T) { defer tests.PrepareTestEnv(t)() defer test.MockVariableValue(&setting.Audit.RecordOutput, setting.AuditRecordOutputDatabase)() - session := loginUser(t, "user1") - session.MakeRequest(t, NewRequest(t, "POST", "/-/admin/users/2/impersonate"), http.StatusOK) + // A leaked token is only traceable if events name the token, not just its owner. + t.Run("TokenCredential", func(t *testing.T) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user/settings/applications", map[string]string{ - "name": "impersonated-token", - "scope-dummy": "read:user", - }), http.StatusOK) + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{ + Name: "audit-credential-repo", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) - session.MakeRequest(t, NewRequest(t, "GET", "/user/logout"), http.StatusSeeOther) - - events, _, err := audit_model.FindEvents(t.Context(), &audit_model.EventSearchOptions{ActorID: 1}) - require.NoError(t, err) - - byAction := make(map[audit_model.Action]*audit_model.Event, len(events)) - for _, e := range events { - byAction[e.Action] = e - } - - start := byAction[audit_model.UserImpersonation] - require.NotNil(t, start) - assert.Equal(t, int64(1), start.ActorID) - assert.Equal(t, int64(2), start.ScopeID) - - token := byAction[audit_model.UserAccessTokenAdd] - require.NotNil(t, token) - assert.Equal(t, int64(2), token.ActorID) // the token really belongs to user2 - assert.Equal(t, int64(1), token.ImpersonatorID) - assert.Equal(t, "user1", token.ImpersonatorName) - assert.Equal(t, audit_model.OriginUI, token.Origin) - assert.NotEmpty(t, token.IPAddress) - - exit := byAction[audit_model.UserImpersonationExit] - require.NotNil(t, exit) - assert.Equal(t, int64(1), exit.ActorID) - assert.Zero(t, exit.ImpersonatorID) -} - -// A leaked token is only traceable if events name the token, not just its owner. -func TestAdminAuditLogTokenCredential(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.Audit.RecordOutput, setting.AuditRecordOutputDatabase)() - - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - - req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{ - Name: "audit-credential-repo", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - events, _, err := audit_model.FindEvents(t.Context(), &audit_model.EventSearchOptions{ - Action: audit_model.RepositoryCreate, - ActorID: 2, + events, _, err := audit_model.FindEvents(t.Context(), &audit_model.EventSearchOptions{ + Action: audit_model.RepositoryCreate, + ActorID: 2, + }) + require.NoError(t, err) + require.Len(t, events, 1) + assert.True(t, strings.HasPrefix(events[0].ActorCredential, "access-token:"), "unexpected credential %q", events[0].ActorCredential) + assert.Equal(t, audit_model.OriginAPI, events[0].Origin) + assert.NotEmpty(t, events[0].IPAddress) + }) + + // Events an admin causes while impersonating must stay traceable to the admin, + // otherwise anything done in an impersonated session is pinned on the victim. + t.Run("Impersonation", func(t *testing.T) { + session := loginUser(t, "user1") + session.MakeRequest(t, NewRequest(t, "POST", "/-/admin/users/2/impersonate"), http.StatusOK) + + session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user/settings/applications", map[string]string{ + "name": "impersonated-token", + "scope-dummy": "read:user", + }), http.StatusOK) + + session.MakeRequest(t, NewRequest(t, "GET", "/user/logout"), http.StatusSeeOther) + + events, _, err := audit_model.FindEvents(t.Context(), &audit_model.EventSearchOptions{ActorID: 1}) + require.NoError(t, err) + + byAction := make(map[audit_model.Action]*audit_model.Event, len(events)) + for _, e := range events { + byAction[e.Action] = e + } + + start := byAction[audit_model.UserImpersonation] + require.NotNil(t, start) + assert.Equal(t, int64(1), start.ActorID) + assert.Equal(t, int64(2), start.ScopeID) + + token := byAction[audit_model.UserAccessTokenAdd] + require.NotNil(t, token) + assert.Equal(t, int64(2), token.ActorID) // the token really belongs to user2 + assert.Equal(t, int64(1), token.ImpersonatorID) + assert.Equal(t, "user1", token.ImpersonatorName) + assert.Equal(t, audit_model.OriginUI, token.Origin) + assert.NotEmpty(t, token.IPAddress) + + exit := byAction[audit_model.UserImpersonationExit] + require.NotNil(t, exit) + assert.Equal(t, int64(1), exit.ActorID) + assert.Zero(t, exit.ImpersonatorID) }) - require.NoError(t, err) - require.Len(t, events, 1) - assert.True(t, strings.HasPrefix(events[0].ActorCredential, "access-token:"), "unexpected credential %q", events[0].ActorCredential) - assert.Equal(t, audit_model.OriginAPI, events[0].Origin) - assert.NotEmpty(t, events[0].IPAddress) } func TestAdminAuditLogExport(t *testing.T) { diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go index c70f0094009..efad9fe160e 100644 --- a/tests/integration/admin_user_test.go +++ b/tests/integration/admin_user_test.go @@ -27,68 +27,66 @@ import ( func TestAdminViewUsers(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user1") - req := NewRequest(t, "GET", "/-/admin/users") - session.MakeRequest(t, req, http.StatusOK) + t.Run("List", func(t *testing.T) { + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/-/admin/users") + session.MakeRequest(t, req, http.StatusOK) - session = loginUser(t, "user2") - req = NewRequest(t, "GET", "/-/admin/users") - session.MakeRequest(t, req, http.StatusForbidden) -} + session = loginUser(t, "user2") + req = NewRequest(t, "GET", "/-/admin/users") + session.MakeRequest(t, req, http.StatusForbidden) + }) -func TestAdminViewUsersFilterAuthSource(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("ViewUser", func(t *testing.T) { + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/-/admin/users/1") + session.MakeRequest(t, req, http.StatusOK) - source := &auth_model.Source{Type: auth_model.LDAP, Name: "test-user-list-filter", IsActive: false, Cfg: &ldap.Source{}} // users stay attached to a deactivated source - require.NoError(t, auth_model.CreateSource(t.Context(), source)) + session = loginUser(t, "user2") + req = NewRequest(t, "GET", "/-/admin/users/1") + session.MakeRequest(t, req, http.StatusForbidden) + }) - user2 := &user_model.User{ID: 2, LoginType: auth_model.LDAP, LoginSource: source.ID} - require.NoError(t, user_model.UpdateUserCols(t.Context(), user2, "login_type", "login_source")) + t.Run("FilterAuthSource", func(t *testing.T) { + source := &auth_model.Source{Type: auth_model.LDAP, Name: "test-user-list-filter", IsActive: false, Cfg: &ldap.Source{}} // users stay attached to a deactivated source + require.NoError(t, auth_model.CreateSource(t.Context(), source)) - session := loginUser(t, "user1") - listUsers := func(query string) (*HTMLDoc, []string) { - req := NewRequest(t, "GET", "/-/admin/users?"+query) - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - return doc, doc.Find("table tbody tr td:nth-child(2) a").Map(func(_ int, s *goquery.Selection) string { - return s.Text() - }) - } + user2 := &user_model.User{ID: 2, LoginType: auth_model.LDAP, LoginSource: source.ID} + require.NoError(t, user_model.UpdateUserCols(t.Context(), user2, "login_type", "login_source")) - doc, users := listUsers("source_id=") // the "All" option submits an empty value - AssertHTMLElement(t, doc, `input[name="source_id"][value=""][checked]`, true) - assert.Subset(t, users, []string{"user1", "user2"}) + session := loginUser(t, "user1") + listUsers := func(query string) (*HTMLDoc, []string) { + req := NewRequest(t, "GET", "/-/admin/users?"+query) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + return doc, doc.Find("table tbody tr td:nth-child(2) a").Map(func(_ int, s *goquery.Selection) string { + return s.Text() + }) + } - doc, users = listUsers(fmt.Sprintf("source_id=%d", source.ID)) // the "test-user-list-filter" LDAP source - AssertHTMLElement(t, doc, fmt.Sprintf(`input[name="source_id"][value="%d"][checked]`, source.ID), true) - assert.Equal(t, []string{"user2"}, users) - assert.Equal(t, source.Name, doc.Find("table tbody tr td:nth-child(4)").Text()) + doc, users := listUsers("source_id=") // the "All" option submits an empty value + AssertHTMLElement(t, doc, `input[name="source_id"][value=""][checked]`, true) + assert.Subset(t, users, []string{"user1", "user2"}) - _, users = listUsers("source_id=0") // 0 means the "Local" source - assert.Contains(t, users, "user1") - assert.NotContains(t, users, "user2") + doc, users = listUsers(fmt.Sprintf("source_id=%d", source.ID)) // the "test-user-list-filter" LDAP source + AssertHTMLElement(t, doc, fmt.Sprintf(`input[name="source_id"][value="%d"][checked]`, source.ID), true) + assert.Equal(t, []string{"user2"}, users) + assert.Equal(t, source.Name, doc.Find("table tbody tr td:nth-child(4)").Text()) - token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin) - req := NewRequest(t, "GET", "/api/v1/admin/users?source_id=0").AddTokenAuth(token) // the API also treats 0 as local users - apiUsers := DecodeJSON(t, MakeRequest(t, req, http.StatusOK), []api.User{}) - apiUserNames := make([]string, 0, len(apiUsers)) - for _, u := range apiUsers { - apiUserNames = append(apiUserNames, u.UserName) - } - assert.Contains(t, apiUserNames, "user1") - assert.NotContains(t, apiUserNames, "user2") -} + _, users = listUsers("source_id=0") // 0 means the "Local" source + assert.Contains(t, users, "user1") + assert.NotContains(t, users, "user2") -func TestAdminViewUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user1") - req := NewRequest(t, "GET", "/-/admin/users/1") - session.MakeRequest(t, req, http.StatusOK) - - session = loginUser(t, "user2") - req = NewRequest(t, "GET", "/-/admin/users/1") - session.MakeRequest(t, req, http.StatusForbidden) + token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin) + req := NewRequest(t, "GET", "/api/v1/admin/users?source_id=0").AddTokenAuth(token) // the API also treats 0 as local users + apiUsers := DecodeJSON(t, MakeRequest(t, req, http.StatusOK), []api.User{}) + apiUserNames := make([]string, 0, len(apiUsers)) + for _, u := range apiUsers { + apiUserNames = append(apiUserNames, u.UserName) + } + assert.Contains(t, apiUserNames, "user1") + assert.NotContains(t, apiUserNames, "user2") + }) } func TestAdminEditUser(t *testing.T) { diff --git a/tests/integration/api_actions_artifact_test.go b/tests/integration/api_actions_artifact_test.go index 86ec2c18c15..618a8583831 100644 --- a/tests/integration/api_actions_artifact_test.go +++ b/tests/integration/api_actions_artifact_test.go @@ -189,39 +189,85 @@ func TestActionsJobSummaryUpload(t *testing.T) { }) } -func TestActionsArtifactUploadSingleFile(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() +func TestActionsArtifactUpload(t *testing.T) { + type uploadingFile struct { + Path string + Content string + MD5 string + } - // acquire artifact upload url - req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ - Type: "actions_storage", - Name: "artifact", - }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - resp := MakeRequest(t, req, http.StatusOK) - uploadResp := DecodeJSON(t, resp, &uploadArtifactResponse{}) - assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") + for _, test := range []struct { + name string + artifactName string + confirmName string + retentionDays int64 + files []uploadingFile + }{ + { + name: "single-file", + artifactName: "artifact", + confirmName: "artifact-single", + files: []uploadingFile{{"abc-2.txt", strings.Repeat("C", 1024), "XVlf820rMInUi64wmMi6EA=="}}, + }, + { + name: "multiple-files", + artifactName: "multi-files", + confirmName: "multi-files", + files: []uploadingFile{ + {"abc-3.txt", strings.Repeat("D", 1024), "9nqj7E8HZmfQtPifCJ5Zww=="}, + {"xyz/def-2.txt", strings.Repeat("E", 1024), "/s1kKvxeHlUX85vaTaVxuA=="}, + }, + }, + { + name: "retention-days", + artifactName: "artifact-retention-days", + confirmName: "artifact-retention-days", + retentionDays: 9, + files: []uploadingFile{{"abc.txt", strings.Repeat("A", 1024), "1HsSe8LeLWh93ILaw1TEFQ=="}}, + }, + } { + t.Run(test.name, func(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() - // get upload url - idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=artifact/abc-2.txt" + // acquire artifact upload url + req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ + Type: "actions_storage", + Name: test.artifactName, + RetentionDays: test.retentionDays, + }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") + resp := MakeRequest(t, req, http.StatusOK) + uploadResp := DecodeJSON(t, resp, &uploadArtifactResponse{}) + assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") - // upload artifact chunk - body := strings.Repeat("C", 1024) - req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). - SetHeader("Content-Range", "bytes 0-1023/1024"). - SetHeader("Content-Encoding", "video/mp4,text/html"). - SetHeader("x-tfs-filelength", "1024"). - SetHeader("x-actions-results-md5", "XVlf820rMInUi64wmMi6EA==") // base64(md5(body)) - MakeRequest(t, req, http.StatusOK) - assert.Empty(t, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionArtifact{RunID: 791, ArtifactName: "artifact", ArtifactPath: "abc-2.txt"}).ContentEncodingOrType) + querySeparator := "?" + if test.retentionDays != 0 { + assert.Contains(t, uploadResp.FileContainerResourceURL, "?retentionDays=9") + querySeparator = "&" + } + for _, file := range test.files { + // get upload url + idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") + url := uploadResp.FileContainerResourceURL[idx:] + querySeparator + "itemPath=" + test.artifactName + "/" + file.Path - t.Logf("Create artifact confirm") + // upload artifact chunk + req = NewRequestWithBody(t, "PUT", url, strings.NewReader(file.Content)). + AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). + SetHeader("Content-Range", "bytes 0-1023/1024"). + SetHeader("Content-Encoding", "video/mp4,text/html"). + SetHeader("x-tfs-filelength", "1024"). + SetHeader("x-actions-results-md5", file.MD5) // base64(md5(body)) + MakeRequest(t, req, http.StatusOK) + assert.Empty(t, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionArtifact{RunID: 791, ArtifactName: test.artifactName, ArtifactPath: file.Path}).ContentEncodingOrType) + } - // confirm artifact upload - req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-single"). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - MakeRequest(t, req, http.StatusOK) + t.Logf("Create artifact confirm") + + // confirm artifact upload + req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName="+test.confirmName). + AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") + MakeRequest(t, req, http.StatusOK) + }) + } } func TestActionsArtifactUploadInvalidHash(t *testing.T) { @@ -316,61 +362,6 @@ func TestActionsArtifactDownload(t *testing.T) { assert.Equal(t, body, resp.Body.String()) } -func TestActionsArtifactUploadMultipleFile(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - const testArtifactName = "multi-files" - - // acquire artifact upload url - req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ - Type: "actions_storage", - Name: testArtifactName, - }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - resp := MakeRequest(t, req, http.StatusOK) - uploadResp := DecodeJSON(t, resp, &uploadArtifactResponse{}) - assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") - - type uploadingFile struct { - Path string - Content string - MD5 string - } - - files := []uploadingFile{ - { - Path: "abc-3.txt", - Content: strings.Repeat("D", 1024), - MD5: "9nqj7E8HZmfQtPifCJ5Zww==", - }, - { - Path: "xyz/def-2.txt", - Content: strings.Repeat("E", 1024), - MD5: "/s1kKvxeHlUX85vaTaVxuA==", - }, - } - - for _, f := range files { - // get upload url - idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=" + testArtifactName + "/" + f.Path - - // upload artifact chunk - req = NewRequestWithBody(t, "PUT", url, strings.NewReader(f.Content)). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). - SetHeader("Content-Range", "bytes 0-1023/1024"). - SetHeader("x-tfs-filelength", "1024"). - SetHeader("x-actions-results-md5", f.MD5) // base64(md5(body)) - MakeRequest(t, req, http.StatusOK) - } - - t.Logf("Create artifact confirm") - - // confirm artifact upload - req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName="+testArtifactName). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - MakeRequest(t, req, http.StatusOK) -} - func TestActionsArtifactDownloadMultiFiles(t *testing.T) { defer prepareTestEnvActionsArtifacts(t)() @@ -424,41 +415,6 @@ func TestActionsArtifactDownloadMultiFiles(t *testing.T) { } } -func TestActionsArtifactUploadWithRetentionDays(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - // acquire artifact upload url - req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ - Type: "actions_storage", - Name: "artifact-retention-days", - RetentionDays: 9, - }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - resp := MakeRequest(t, req, http.StatusOK) - uploadResp := DecodeJSON(t, resp, &uploadArtifactResponse{}) - assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") - assert.Contains(t, uploadResp.FileContainerResourceURL, "?retentionDays=9") - - // get upload url - idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := uploadResp.FileContainerResourceURL[idx:] + "&itemPath=artifact-retention-days/abc.txt" - - // upload artifact chunk - body := strings.Repeat("A", 1024) - req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). - SetHeader("Content-Range", "bytes 0-1023/1024"). - SetHeader("x-tfs-filelength", "1024"). - SetHeader("x-actions-results-md5", "1HsSe8LeLWh93ILaw1TEFQ==") // base64(md5(body)) - MakeRequest(t, req, http.StatusOK) - - t.Logf("Create artifact confirm") - - // confirm artifact upload - req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-retention-days"). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - MakeRequest(t, req, http.StatusOK) -} - func TestActionsArtifactOverwrite(t *testing.T) { defer prepareTestEnvActionsArtifacts(t)() diff --git a/tests/integration/api_actions_artifact_v4_test.go b/tests/integration/api_actions_artifact_v4_test.go index ddb4515ee9a..72662a128bc 100644 --- a/tests/integration/api_actions_artifact_v4_test.go +++ b/tests/integration/api_actions_artifact_v4_test.go @@ -756,46 +756,30 @@ func TestActionsArtifactV4ListAndGetPublicApi(t *testing.T) { } } -func TestActionsArtifactV4GetArtifactMismatchedRepoNotFound(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() +func TestActionsArtifactV4ArtifactAccess(t *testing.T) { + for _, testCase := range []struct { + name string + repoID int64 + suffix string + statusCode int + }{ + {"GetArtifactMismatchedRepoNotFound", 1, "", http.StatusNotFound}, + {"DownloadArtifactMismatchedRepoNotFound", 1, "/zip", http.StatusNotFound}, + {"DownloadArtifactCorrectRepoFound", 4, "/zip", http.StatusFound}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: testCase.repoID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - // confirm artifacts of wrong repo is not visible - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestActionsArtifactV4DownloadArtifactMismatchedRepoNotFound(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // confirm artifacts of wrong repo is not visible - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestActionsArtifactV4DownloadArtifactCorrectRepoFound(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // confirm artifacts of correct repo is visible - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusFound) + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d%s", repo.FullName(), 22, testCase.suffix), nil). + AddTokenAuth(token) + MakeRequest(t, req, testCase.statusCode) + }) + } } func TestActionsArtifactV4Delete(t *testing.T) { @@ -837,51 +821,37 @@ func TestActionsArtifactV4Delete(t *testing.T) { } func TestActionsArtifactV4DeletePublicApi(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() + for _, testCase := range []struct { + scope auth_model.AccessTokenScope + deleteStatus int + getStatus int + }{ + {auth_model.AccessTokenScopeWriteRepository, http.StatusNoContent, http.StatusNotFound}, + {auth_model.AccessTokenScopeReadRepository, http.StatusForbidden, http.StatusOK}, + } { + t.Run(string(testCase.scope), func(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, testCase.scope) - // confirm artifacts exists - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + // confirm artifacts exists + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) - // delete artifact by id - req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + // delete artifact by id + req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, testCase.deleteStatus) - // confirm artifacts has been deleted - req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestActionsArtifactV4DeletePublicApiNotAllowedReadScope(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - // confirm artifacts exists - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - - // try delete artifact by id - req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - // confirm artifacts has not been deleted - req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, testCase.getStatus) + }) + } } func testActionRunAttemptArtifactV4(t *testing.T, repo *repo_model.Repository, session *TestSession, runner *mockRunner) { diff --git a/tests/integration/api_actions_run_test.go b/tests/integration/api_actions_run_test.go index d28d8e2c643..1bb31b5da09 100644 --- a/tests/integration/api_actions_run_test.go +++ b/tests/integration/api_actions_run_test.go @@ -198,13 +198,11 @@ func TestAPIActionsRerunWorkflowRun(t *testing.T) { session := loginUser(t, user.Name) writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793/rerun", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793/jobs/194/rerun", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusBadRequest) + for _, endpoint := range []string{"runs/793/rerun", "runs/793/jobs/194/rerun"} { + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/%s", repo.FullName(), endpoint)). + AddTokenAuth(writeToken) + MakeRequest(t, req, http.StatusBadRequest) + } }) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) @@ -312,41 +310,30 @@ func TestAPIActionsRerunWorkflowRun(t *testing.T) { require.NoError(t, err) require.True(t, hasLatestAttempt) - job198 := getLatestAttemptJobByTemplateJobID(t, 795, 198) - assert.Equal(t, actions_model.StatusWaiting, job198.Status) - assert.Equal(t, latestAttempt.Attempt, job198.Attempt) - assert.Equal(t, int64(0), job198.TaskID) - - job199 := getLatestAttemptJobByTemplateJobID(t, 795, 199) - assert.Equal(t, actions_model.StatusWaiting, job199.Status) - assert.Equal(t, latestAttempt.Attempt, job199.Attempt) - assert.Equal(t, int64(0), job199.TaskID) + for _, jobID := range []int64{198, 199} { + job := getLatestAttemptJobByTemplateJobID(t, 795, jobID) + assert.Equal(t, actions_model.StatusWaiting, job.Status) + assert.Equal(t, latestAttempt.Attempt, job.Attempt) + assert.Equal(t, int64(0), job.TaskID) + } }) - t.Run("ForbiddenWithoutWriteScope", func(t *testing.T) { - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/rerun", repo.FullName())). - AddTokenAuth(readToken) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("NotFound", func(t *testing.T) { - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/999999/rerun", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("NotFoundJob", func(t *testing.T) { - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs/999999/rerun", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("NoLogsAfterRerun", func(t *testing.T) { + for _, testCase := range []struct { + name, method, endpoint, token string + status int + }{ + {"ForbiddenWithoutWriteScope", "POST", "runs/795/rerun", readToken, http.StatusForbidden}, + {"NotFound", "POST", "runs/999999/rerun", writeToken, http.StatusNotFound}, + {"NotFoundJob", "POST", "runs/795/jobs/999999/rerun", writeToken, http.StatusNotFound}, // the full rerun above cleared both TaskID and SourceTaskID on every latest-attempt job - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/logs", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusNotFound) - }) + {"NoLogsAfterRerun", "GET", "runs/795/logs", writeToken, http.StatusNotFound}, + } { + t.Run(testCase.name, func(t *testing.T) { + req := NewRequest(t, testCase.method, fmt.Sprintf("/api/v1/repos/%s/actions/%s", repo.FullName(), testCase.endpoint)). + AddTokenAuth(testCase.token) + MakeRequest(t, req, testCase.status) + }) + } } func testAPIActionsCancelWorkflowRun(t *testing.T) { @@ -744,39 +731,29 @@ func testAPIActionsGetWorkflowJobLogsNotFound(t *testing.T) { func TestAPIOrgActionsRunsAccessControl(t *testing.T) { defer tests.PrepareTestEnv(t)() + checkAccess := func(token, runMessage, jobMessage string) { + req := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runs").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + runs := DecodeJSON(t, resp, &api.ActionWorkflowRunsResponse{}) + for _, run := range runs.Entries { + assert.NotEqual(t, int64(802), run.ID, runMessage) + } + + req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/jobs").AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + jobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{}) + for _, job := range jobs.Entries { + assert.NotEqual(t, int64(802), job.RunID, jobMessage) + } + } + // org3 has action run 802 (and its jobs) in the private repo5; user28 is an org3 member // (teams 12/13) with no access to repo5. token := getUserToken(t, "user28", auth_model.AccessTokenScopeReadOrganization) - - req := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runs").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - runs := DecodeJSON(t, resp, &api.ActionWorkflowRunsResponse{}) - for _, r := range runs.Entries { - assert.NotEqual(t, int64(802), r.ID, "must not leak a run from an inaccessible repo") - } - - req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/jobs").AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - jobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{}) - for _, j := range jobs.Entries { - assert.NotEqual(t, int64(802), j.RunID, "must not leak a job from an inaccessible repo run") - } + checkAccess(token, "must not leak a run from an inaccessible repo", "must not leak a job from an inaccessible repo run") // user1 is a site admin: it normally bypasses the per-repo access filter, but a public-only token // must stay confined to public repos, so the run/job in the private repo5 must not be listed. adminPublicOnly := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly) - - req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runs").AddTokenAuth(adminPublicOnly) - resp = MakeRequest(t, req, http.StatusOK) - adminRuns := DecodeJSON(t, resp, &api.ActionWorkflowRunsResponse{}) - for _, r := range adminRuns.Entries { - assert.NotEqual(t, int64(802), r.ID, "a public-only admin token must not list a private repo's run") - } - - req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/jobs").AddTokenAuth(adminPublicOnly) - resp = MakeRequest(t, req, http.StatusOK) - adminJobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{}) - for _, j := range adminJobs.Entries { - assert.NotEqual(t, int64(802), j.RunID, "a public-only admin token must not list a private repo's job") - } + checkAccess(adminPublicOnly, "a public-only admin token must not list a private repo's run", "a public-only admin token must not list a private repo's job") } diff --git a/tests/integration/api_admin_org_test.go b/tests/integration/api_admin_org_test.go index 8fefe51ba9a..6e6b114ea2d 100644 --- a/tests/integration/api_admin_org_test.go +++ b/tests/integration/api_admin_org_test.go @@ -22,64 +22,48 @@ func TestAPIAdminOrgCreate(t *testing.T) { session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) - t.Run("CreateOrg", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - org := api.CreateOrgOption{ - UserName: "user2_org", - FullName: "User2's organization", - Description: "This organization created by admin for user2", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "private", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) + for _, testCase := range []struct { + name string + visibility api.VisibilityString + status int + }{ + {"CreateOrg", "private", http.StatusCreated}, + {"CreateBadVisibility", "notvalid", http.StatusUnprocessableEntity}, + {"CreateNotAdmin", "public", http.StatusForbidden}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testToken := token + if testCase.status == http.StatusForbidden { + session := loginUser(t, "user2") + testToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + } + org := api.CreateOrgOption{ + UserName: "user2_org", + FullName: "User2's organization", + Description: "This organization created by admin for user2", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: testCase.visibility, + } + req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). + AddTokenAuth(testToken) + resp := MakeRequest(t, req, testCase.status) + if testCase.status == http.StatusCreated { + apiOrg := DecodeJSON(t, resp, &api.Organization{}) + assert.Equal(t, org.UserName, apiOrg.Name) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) - apiOrg := DecodeJSON(t, resp, &api.Organization{}) - - assert.Equal(t, org.UserName, apiOrg.Name) - assert.Equal(t, org.FullName, apiOrg.FullName) - assert.Equal(t, org.Description, apiOrg.Description) - assert.Equal(t, org.Website, apiOrg.Website) - assert.Equal(t, org.Location, apiOrg.Location) - assert.Equal(t, org.Visibility, apiOrg.Visibility) - - unittest.AssertExistsAndLoadBean(t, &user_model.User{ - Name: org.UserName, - LowerName: strings.ToLower(org.UserName), - FullName: org.FullName, + unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: org.UserName, + LowerName: strings.ToLower(org.UserName), + FullName: org.FullName, + }) + } }) - }) - t.Run("CreateBadVisibility", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - org := api.CreateOrgOption{ - UserName: "user2_org", - FullName: "User2's organization", - Description: "This organization created by admin for user2", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "notvalid", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - t.Run("CreateNotAdmin", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - nonAdminUsername := "user2" - session := loginUser(t, nonAdminUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) - org := api.CreateOrgOption{ - UserName: "user2_org", - FullName: "User2's organization", - Description: "This organization created by admin for user2", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "public", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - }) + } } diff --git a/tests/integration/api_admin_packages_test.go b/tests/integration/api_admin_packages_test.go deleted file mode 100644 index 11ef8910502..00000000000 --- a/tests/integration/api_admin_packages_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "bytes" - "fmt" - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestAPIAdminListPackages(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - packageName := "admin-test-package" - packageVersion := "1.0.0" - - url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user4.Name, packageName, packageVersion) - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})).AddBasicAuth(user4.Name) - MakeRequest(t, req, http.StatusCreated) - - adminToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin) - req = NewRequest(t, "GET", "/api/v1/admin/packages").AddTokenAuth(adminToken) - resp := MakeRequest(t, req, http.StatusOK) - - apiPackages := DecodeJSON(t, resp, []*api.Package{}) - actual := map[string]any{} - for _, p := range apiPackages { - actual[p.Name] = map[string]any{"ownerName": p.Owner.UserName, "type": p.Type, "version": p.Version} - } - assert.Equal(t, map[string]any{ - packageName: map[string]any{"ownerName": user4.Name, "type": "generic", "version": packageVersion}, - }, actual) -} diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index dfcacdf0687..dccf666e2e2 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -4,6 +4,7 @@ package integration import ( + "bytes" "fmt" "net/http" "testing" @@ -23,140 +24,205 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { +func TestAPIAdminListPackages(t *testing.T) { defer tests.PrepareTestEnv(t)() - // user1 is an admin user - session := loginUser(t, "user1") - keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", keyOwner.Name) - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - "title": "test-key", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + packageName := "admin-test-package" + packageVersion := "1.0.0" - newPublicKey := DecodeJSON(t, resp, &api.PublicKey{}) - unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{ - ID: newPublicKey.ID, - Name: newPublicKey.Title, - Fingerprint: newPublicKey.Fingerprint, - OwnerID: keyOwner.ID, + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user4.Name, packageName, packageVersion) + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})).AddBasicAuth(user4.Name) + MakeRequest(t, req, http.StatusCreated) + + adminToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin) + req = NewRequest(t, "GET", "/api/v1/admin/packages").AddTokenAuth(adminToken) + resp := MakeRequest(t, req, http.StatusOK) + + apiPackages := DecodeJSON(t, resp, []*api.Package{}) + actual := map[string]any{} + for _, p := range apiPackages { + actual[p.Name] = map[string]any{"ownerName": p.Owner.UserName, "type": p.Type, "version": p.Version} + } + assert.Equal(t, map[string]any{ + packageName: map[string]any{"ownerName": user4.Name, "type": "generic", "version": packageVersion}, + }, actual) +} + +func TestAPIAdminAccessAndSSHKeys(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("ListUsers", func(t *testing.T) { + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin) + + req := NewRequest(t, "GET", "/api/v1/admin/users"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + users := DecodeJSON(t, resp, []api.User{}) + + found := false + for _, user := range users { + if user.UserName == adminUsername { + found = true + } + } + assert.True(t, found) + numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0") + assert.Len(t, users, numberOfUsers) }) - req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", keyOwner.Name, newPublicKey.ID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &asymkey_model.PublicKey{ID: newPublicKey.ID}) -} + t.Run("ListUsersNotLoggedIn", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/admin/users") + MakeRequest(t, req, http.StatusUnauthorized) + }) -func TestAPIAdminDeleteMissingSSHKey(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("ListUsersNonAdmin", func(t *testing.T) { + nonAdminUsername := "user2" + token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll) + req := NewRequest(t, "GET", "/api/v1/admin/users"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) - // user1 is an admin user - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteAdmin) - req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d", unittest.NonexistentID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} + t.Run("SudoUser", func(t *testing.T) { + adminUsername := "user1" + normalUsername := "user2" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser) -func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - normalUsername := "user2" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + req := NewRequest(t, "GET", "/api/v1/user?sudo="+normalUsername). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + user := DecodeJSON(t, resp, &api.User{}) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", adminUsername) - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - "title": "test-key", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - newPublicKey := DecodeJSON(t, resp, &api.PublicKey{}) + assert.Equal(t, normalUsername, user.UserName) + }) - token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll) - req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} + t.Run("SudoUserForbidden", func(t *testing.T) { + adminUsername := "user1" + normalUsername := "user2" -func TestAPISudoUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - normalUsername := "user2" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser) + token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin) + req := NewRequest(t, "GET", "/api/v1/user?sudo="+adminUsername). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) - req := NewRequest(t, "GET", "/api/v1/user?sudo="+normalUsername). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - user := DecodeJSON(t, resp, &api.User{}) + t.Run("CreateUserInvalidEmail", func(t *testing.T) { + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{ + "email": "invalid_email@domain.com\r\n", + "full_name": "invalid user", + "login_name": "invalidUser", + "must_change_password": "true", + "password": "password", + "send_notify": "true", + "source_id": "0", + "username": "invalidUser", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + }) - assert.Equal(t, normalUsername, user.UserName) -} + t.Run("DeleteMissingSSHKey", func(t *testing.T) { + // user1 is an admin user + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteAdmin) + req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d", unittest.NonexistentID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) -func TestAPISudoUserForbidden(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - normalUsername := "user2" + t.Run("Cron", func(t *testing.T) { + // user1 is an admin user + session := loginUser(t, "user1") - token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin) - req := NewRequest(t, "GET", "/api/v1/user?sudo="+adminUsername). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} + t.Run("List", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() -func TestAPIListUsers(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) - req := NewRequest(t, "GET", "/api/v1/admin/users"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - users := DecodeJSON(t, resp, []api.User{}) + req := NewRequest(t, "GET", "/api/v1/admin/cron"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - found := false - for _, user := range users { - if user.UserName == adminUsername { - found = true - } - } - assert.True(t, found) - numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0") - assert.Len(t, users, numberOfUsers) -} + assert.Equal(t, "30", resp.Header().Get("X-Total-Count")) -func TestAPIListUsersNotLoggedIn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/admin/users") - MakeRequest(t, req, http.StatusUnauthorized) -} + crons := DecodeJSON(t, resp, []api.Cron{}) + assert.Len(t, crons, 30) + }) -func TestAPIListUsersNonAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - nonAdminUsername := "user2" - token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll) - req := NewRequest(t, "GET", "/api/v1/admin/users"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} + t.Run("Execute", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() -func TestAPICreateUserInvalidEmail(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{ - "email": "invalid_email@domain.com\r\n", - "full_name": "invalid user", - "login_name": "invalidUser", - "must_change_password": "true", - "password": "password", - "send_notify": "true", - "source_id": "0", - "username": "invalidUser", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + now := time.Now() + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + // Archive cleanup is harmless, because in the test environment there are none + // and is thus an NOOP operation and therefore doesn't interfere with any other + // tests. + req := NewRequest(t, "POST", "/api/v1/admin/cron/archive_cleanup"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + // Check for the latest run time for this cron, to ensure it has been run. + req = NewRequest(t, "GET", "/api/v1/admin/cron"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + crons := DecodeJSON(t, resp, []api.Cron{}) + + for _, cron := range crons { + if cron.Name == "archive_cleanup" { + assert.True(t, now.Before(cron.Prev)) + } + } + }) + }) + + t.Run("CreateAndDeleteSSHKey", func(t *testing.T) { + // user1 is an admin user + session := loginUser(t, "user1") + keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", keyOwner.Name) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + "title": "test-key", + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + newPublicKey := DecodeJSON(t, resp, &api.PublicKey{}) + unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{ + ID: newPublicKey.ID, + Name: newPublicKey.Title, + Fingerprint: newPublicKey.Fingerprint, + OwnerID: keyOwner.ID, + }) + + req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", keyOwner.Name, newPublicKey.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &asymkey_model.PublicKey{ID: newPublicKey.ID}) + }) + + t.Run("DeleteUnauthorizedKey", func(t *testing.T) { + adminUsername := "user1" + normalUsername := "user2" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + + urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", adminUsername) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + "title": "test-key", + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + newPublicKey := DecodeJSON(t, resp, &api.PublicKey{}) + + token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll) + req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) } func TestAPICreateAndDeleteUser(t *testing.T) { @@ -239,87 +305,24 @@ func TestAPIRenameUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s/rename", "user2") - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "User2", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2") - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "User2-2-2", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "user1", - }).AddTokenAuth(token) - // the old user name still be used by with a redirect - MakeRequest(t, req, http.StatusTemporaryRedirect) - - urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2-2-2") - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "user1", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "user2", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) -} - -func TestAPICron(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user1 is an admin user - session := loginUser(t, "user1") - - t.Run("List", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) - - req := NewRequest(t, "GET", "/api/v1/admin/cron"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, "30", resp.Header().Get("X-Total-Count")) - - crons := DecodeJSON(t, resp, []api.Cron{}) - assert.Len(t, crons, 30) - }) - - t.Run("Execute", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - now := time.Now() - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) - // Archive cleanup is harmless, because in the test environment there are none - // and is thus an NOOP operation and therefore doesn't interfere with any other - // tests. - req := NewRequest(t, "POST", "/api/v1/admin/cron/archive_cleanup"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Check for the latest run time for this cron, to ensure it has been run. - req = NewRequest(t, "GET", "/api/v1/admin/cron"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - crons := DecodeJSON(t, resp, []api.Cron{}) - - for _, cron := range crons { - if cron.Name == "archive_cleanup" { - assert.True(t, now.Before(cron.Prev)) - } - } - }) + for _, testCase := range []struct { + username string + newName string + status int + }{ + {"user2", "User2", http.StatusNoContent}, + {"User2", "User2-2-2", http.StatusNoContent}, + // the old user name still be used by with a redirect + {"User2", "user1", http.StatusTemporaryRedirect}, + {"User2-2-2", "user1", http.StatusUnprocessableEntity}, + {"User2-2-2", "user2", http.StatusNoContent}, + } { + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/admin/users/%s/rename", testCase.username), map[string]string{ + // required + "new_name": testCase.newName, + }).AddTokenAuth(token) + MakeRequest(t, req, testCase.status) + } } func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) { diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 22f6472e0be..2c35df89cfb 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -91,21 +91,6 @@ func testAPIDeleteBranch(t *testing.T, branchName string, expectedHTTPStatus int MakeRequest(t, req, expectedHTTPStatus) } -func TestAPIGetBranch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - for _, test := range []struct { - BranchName string - Exists bool - }{ - {"master", true}, - {"master/doesnotexist", false}, - {"feature/1", true}, - {"feature/1/doesnotexist", false}, - } { - testAPIGetBranch(t, test.BranchName, test.Exists) - } -} - func TestAPICreateBranch(t *testing.T) { onGiteaRun(t, testAPICreateBranches) } @@ -275,8 +260,6 @@ func TestAPIRenameBranch(t *testing.T) { } func TestAPIUpdateBranchReference(t *testing.T) { - defer tests.PrepareTestEnv(t)() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { ctx := NewAPITestContext(t, "user2", "update-branch", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) giteaURL.Path = ctx.GitPath() @@ -312,34 +295,23 @@ func TestAPIUpdateBranchReference(t *testing.T) { assert.NotEmpty(t, newCommit) })(t) - updateReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ - NewCommitID: newCommit, - OldCommitID: featureInitialCommit, - }).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, updateReq, http.StatusNoContent) + updateBranch := func(newCommitID, oldCommitID string, force bool, expectedStatus int) { + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ + NewCommitID: newCommitID, + OldCommitID: oldCommitID, + Force: force, + }).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, req, expectedStatus) + } + updateBranch(newCommit, featureInitialCommit, false, http.StatusNoContent) t.Run("FastForwardApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) { assert.Equal(t, newCommit, branch.Commit.ID) })) - staleReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ - NewCommitID: newCommit, - OldCommitID: featureInitialCommit, - }).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, staleReq, http.StatusUnprocessableEntity) - - nonFFReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ - NewCommitID: featureInitialCommit, - OldCommitID: newCommit, - }).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, nonFFReq, http.StatusUnprocessableEntity) - - forceReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ - NewCommitID: featureInitialCommit, - OldCommitID: newCommit, - Force: true, - }).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, forceReq, http.StatusNoContent) + updateBranch(newCommit, featureInitialCommit, false, http.StatusUnprocessableEntity) + updateBranch(featureInitialCommit, newCommit, false, http.StatusUnprocessableEntity) + updateBranch(featureInitialCommit, newCommit, true, http.StatusNoContent) t.Run("ForceApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) { assert.Equal(t, featureInitialCommit, branch.Commit.ID) @@ -357,6 +329,19 @@ func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to s func TestAPIBranchProtection(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("GetBranch", func(t *testing.T) { + for _, test := range []struct { + BranchName string + Exists bool + }{ + {"master", true}, + {"master/doesnotexist", false}, + {"feature/1", true}, + {"feature/1/doesnotexist", false}, + } { + testAPIGetBranch(t, test.BranchName, test.Exists) + } + }) t.Run("Basic", testAPIBranchProtectionBasic) t.Run("BypassAllowlistValidation", testAPIBranchProtectionBypassAllowlistValidation) } diff --git a/tests/integration/api_comment_attachment_test.go b/tests/integration/api_comment_attachment_test.go index e7b36e0b27e..45f5401a697 100644 --- a/tests/integration/api_comment_attachment_test.go +++ b/tests/integration/api_comment_attachment_test.go @@ -22,129 +22,106 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIGetCommentAttachment(t *testing.T) { +func TestAPICommentAttachments(t *testing.T) { defer tests.PrepareTestEnv(t)() - - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - assert.NoError(t, comment.LoadIssue(t.Context())) - assert.NoError(t, comment.LoadAttachments(t.Context())) - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: comment.Attachments[0].ID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - t.Run("UnrelatedCommentID", func(t *testing.T) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + t.Run("Get", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + assert.NoError(t, comment.LoadIssue(t.Context())) + assert.NoError(t, comment.LoadAttachments(t.Context())) + attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: comment.Attachments[0].ID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) + + t.Run("UnrelatedCommentID", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) + + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + session.MakeRequest(t, req, http.StatusOK) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). + AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusOK) + + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + + expect := convert.ToAPIAttachment(t.Context(), repo, attachment) + assert.Equal(t, expect.ID, apiAttachment.ID) + assert.Equal(t, expect.Name, apiAttachment.Name) + assert.Equal(t, expect.UUID, apiAttachment.UUID) + assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix()) + assert.Equal(t, expect.DownloadURL, apiAttachment.DownloadURL) }) + t.Run("List", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). - AddTokenAuth(token) - session.MakeRequest(t, req, http.StatusOK) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). - AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusOK) + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID). + AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusOK) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + apiAttachments := DecodeJSON(t, resp, []*api.Attachment{}) + expectedCount := unittest.GetCount(t, &repo_model.Attachment{CommentID: comment.ID}) + assert.Len(t, apiAttachments, expectedCount) - expect := convert.ToAPIAttachment(t.Context(), repo, attachment) - assert.Equal(t, expect.ID, apiAttachment.ID) - assert.Equal(t, expect.Name, apiAttachment.Name) - assert.Equal(t, expect.UUID, apiAttachment.UUID) - assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix()) - assert.Equal(t, expect.DownloadURL, apiAttachment.DownloadURL) -} + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachments[0].ID, CommentID: comment.ID}) + }) + t.Run("Create", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) -func TestAPIListCommentAttachments(t *testing.T) { - defer tests.PrepareTestEnv(t)() + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + for _, test := range []struct { + filename string + status int + }{ + {"file.bad", http.StatusUnprocessableEntity}, + {"image.png", http.StatusCreated}, + } { + t.Run(test.filename, func(t *testing.T) { + body := &bytes.Buffer{} - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + // Setup multi-part + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("attachment", test.filename) + assert.NoError(t, err) + if test.status == http.StatusCreated { + _, err = part.Write(testGeneratePngBytes()) + assert.NoError(t, err) + } + err = writer.Close() + assert.NoError(t, err) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID). - AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusOK) - - apiAttachments := DecodeJSON(t, resp, []*api.Attachment{}) - expectedCount := unittest.GetCount(t, &repo_model.Attachment{CommentID: comment.ID}) - assert.Len(t, apiAttachments, expectedCount) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachments[0].ID, CommentID: comment.ID}) -} - -func TestAPICreateCommentAttachment(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - body := &bytes.Buffer{} - - // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", "image.png") - assert.NoError(t, err) - _, err = part.Write(testGeneratePngBytes()) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). - AddTokenAuth(token). - SetHeader("Content-Type", writer.FormDataContentType()) - resp := session.MakeRequest(t, req, http.StatusCreated) - - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID}) -} - -func TestAPICreateCommentAttachmentWithUnallowedFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - filename := "file.bad" - body := &bytes.Buffer{} - - // Setup multi-part. - writer := multipart.NewWriter(body) - _, err := writer.CreateFormFile("attachment", filename) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). - AddTokenAuth(token). - SetHeader("Content-Type", writer.FormDataContentType()) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) + req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). + AddTokenAuth(token). + SetHeader("Content-Type", writer.FormDataContentType()) + resp := session.MakeRequest(t, req, test.status) + if test.status == http.StatusCreated { + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID}) + } + }) + } + }) } func TestAPIEditCommentAttachment(t *testing.T) { defer tests.PrepareTestEnv(t)() - const newAttachmentName = "newAttachmentName.txt" - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 6}) comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: attachment.CommentID}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) @@ -153,35 +130,26 @@ func TestAPIEditCommentAttachment(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", - repoOwner.Name, repo.Name, comment.ID, attachment.ID) - req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - "name": newAttachmentName, - }).AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusCreated) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID, Name: apiAttachment.Name}) -} - -func TestAPIEditCommentAttachmentWithUnallowedFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 6}) - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: attachment.CommentID}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - filename := "file.bad" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", - repoOwner.Name, repo.Name, comment.ID, attachment.ID) - req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - "name": filename, - }).AddTokenAuth(token) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) + for _, test := range []struct { + filename string + status int + }{ + {"file.bad", http.StatusUnprocessableEntity}, + {"newAttachmentName.txt", http.StatusCreated}, + } { + t.Run(test.filename, func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", + repoOwner.Name, repo.Name, comment.ID, attachment.ID) + req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ + "name": test.filename, + }).AddTokenAuth(token) + resp := session.MakeRequest(t, req, test.status) + if test.status == http.StatusCreated { + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID, Name: apiAttachment.Name}) + } + }) + } } func TestAPIDeleteCommentAttachment(t *testing.T) { diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index 8ef14256d6b..d60c3482604 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -21,68 +21,143 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIListRepoComments(t *testing.T) { +func TestAPIComments(t *testing.T) { defer tests.PrepareTestEnv(t)() - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, - unittest.Cond("type = ?", issues_model.CommentTypeComment)) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments", repoOwner.Name, repo.Name)) - req := NewRequest(t, "GET", link.String()) - resp := MakeRequest(t, req, http.StatusOK) - - apiComments := DecodeJSON(t, resp, []*api.Comment{}) - assert.Len(t, apiComments, 2) - for _, apiComment := range apiComments { - c := &issues_model.Comment{ID: apiComment.ID} - unittest.AssertExistsAndLoadBean(t, c, + t.Run("ListRepoComments", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, unittest.Cond("type = ?", issues_model.CommentTypeComment)) - unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: c.IssueID, RepoID: repo.ID}) - } + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - // test before and since filters - query := url.Values{} - before := "2000-01-01T00:00:11+00:00" // unix: 946684811 - since := "2000-01-01T00:00:12+00:00" // unix: 946684812 - query.Add("before", before) - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = MakeRequest(t, req, http.StatusOK) - apiComments = DecodeJSON(t, resp, []*api.Comment{}) - assert.Len(t, apiComments, 1) - assert.EqualValues(t, 2, apiComments[0].ID) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments", repoOwner.Name, repo.Name)) + req := NewRequest(t, "GET", link.String()) + resp := MakeRequest(t, req, http.StatusOK) - query.Del("before") - query.Add("since", since) - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = MakeRequest(t, req, http.StatusOK) - apiComments = DecodeJSON(t, resp, []*api.Comment{}) - assert.Len(t, apiComments, 1) - assert.EqualValues(t, 3, apiComments[0].ID) -} + apiComments := DecodeJSON(t, resp, []*api.Comment{}) + assert.Len(t, apiComments, 2) + for _, apiComment := range apiComments { + c := &issues_model.Comment{ID: apiComment.ID} + unittest.AssertExistsAndLoadBean(t, c, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: c.IssueID, RepoID: repo.ID}) + } -func TestAPIListIssueComments(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // test before and since filters + query := url.Values{} + before := "2000-01-01T00:00:11+00:00" // unix: 946684811 + since := "2000-01-01T00:00:12+00:00" // unix: 946684812 + query.Add("before", before) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + apiComments = DecodeJSON(t, resp, []*api.Comment{}) + assert.Len(t, apiComments, 1) + assert.EqualValues(t, 2, apiComments[0].ID) - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, - unittest.Cond("type = ?", issues_model.CommentTypeComment)) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + query.Del("before") + query.Add("since", since) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + apiComments = DecodeJSON(t, resp, []*api.Comment{}) + assert.Len(t, apiComments, 1) + assert.EqualValues(t, 3, apiComments[0].ID) + }) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/comments", repoOwner.Name, repo.Name, issue.Index). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + t.Run("ListIssueComments", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - comments := DecodeJSON(t, resp, []*api.Comment{}) - expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}, - unittest.Cond("type = ?", issues_model.CommentTypeComment)) - assert.Len(t, comments, expectedCount) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/comments", repoOwner.Name, repo.Name, issue.Index). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + comments := DecodeJSON(t, resp, []*api.Comment{}) + expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + assert.Len(t, comments, expectedCount) + }) + + t.Run("GetComment", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + assert.NoError(t, comment.LoadIssue(t.Context())) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) + MakeRequest(t, req, http.StatusOK) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + apiComment := DecodeJSON(t, resp, &api.Comment{}) + + assert.NoError(t, comment.LoadPoster(t.Context())) + expect := convert.ToAPIComment(t.Context(), repo, comment) + + assert.Equal(t, expect.ID, apiComment.ID) + assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName) + assert.Equal(t, expect.Body, apiComment.Body) + assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix()) + }) + + t.Run("ListIssueTimeline", func(t *testing.T) { + // load comment + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // make request + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/timeline", repoOwner.Name, repo.Name, issue.Index) + resp := MakeRequest(t, req, http.StatusOK) + + // check if lens of list returned by API and + // lists extracted directly from DB are the same + comments := DecodeJSON(t, resp, []*api.TimelineComment{}) + expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}) + assert.Len(t, comments, expectedCount) + }) + + t.Run("GetSystemUserComment", func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + for _, systemUser := range []*user_model.User{ + user_model.NewGhostUser(), + user_model.NewActionsUser(), + } { + body := "Hello " + systemUser.Name + comment, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeComment, + Doer: systemUser, + Repo: repo, + Issue: issue, + Content: body, + }) + assert.NoError(t, err) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) + resp := MakeRequest(t, req, http.StatusOK) + + apiComment := DecodeJSON(t, resp, &api.Comment{}) + + if assert.NotNil(t, apiComment.Poster) { + if assert.Equal(t, systemUser.ID, apiComment.Poster.ID) { + assert.NoError(t, comment.LoadPoster(t.Context())) + assert.Equal(t, systemUser.Name, apiComment.Poster.UserName) + } + } + assert.Equal(t, body, apiComment.Body) + } + }) } func TestAPICreateComment(t *testing.T) { @@ -105,92 +180,25 @@ func TestAPICreateComment(t *testing.T) { assert.Equal(t, commentBody, updatedComment.Body) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody}) - t.Run("BlockedByRepoOwner", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", repo.OwnerName, repo.Name, issue.Index), map[string]string{ - "body": commentBody, - }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("BlockedByIssuePoster", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 13}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", repo.OwnerName, repo.Name, issue.Index), map[string]string{ - "body": commentBody, - }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) - MakeRequest(t, req, http.StatusForbidden) - }) -} - -func TestAPIGetComment(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - assert.NoError(t, comment.LoadIssue(t.Context())) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) - MakeRequest(t, req, http.StatusOK) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiComment := DecodeJSON(t, resp, &api.Comment{}) - - assert.NoError(t, comment.LoadPoster(t.Context())) - expect := convert.ToAPIComment(t.Context(), repo, comment) - - assert.Equal(t, expect.ID, apiComment.ID) - assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName) - assert.Equal(t, expect.Body, apiComment.Body) - assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix()) -} - -func TestAPIGetSystemUserComment(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - for _, systemUser := range []*user_model.User{ - user_model.NewGhostUser(), - user_model.NewActionsUser(), + for _, tc := range []struct { + name string + issueID int64 + }{ + {"BlockedByRepoOwner", 1}, + {"BlockedByIssuePoster", 13}, } { - body := "Hello " + systemUser.Name - comment, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{ - Type: issues_model.CommentTypeComment, - Doer: systemUser, - Repo: repo, - Issue: issue, - Content: body, + t.Run(tc.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: tc.issueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", repo.OwnerName, repo.Name, issue.Index), map[string]string{ + "body": commentBody, + }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) + MakeRequest(t, req, http.StatusForbidden) }) - assert.NoError(t, err) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) - resp := MakeRequest(t, req, http.StatusOK) - - apiComment := DecodeJSON(t, resp, &api.Comment{}) - - if assert.NotNil(t, apiComment.Poster) { - if assert.Equal(t, systemUser.ID, apiComment.Poster.ID) { - assert.NoError(t, comment.LoadPoster(t.Context())) - assert.Equal(t, systemUser.Name, apiComment.Poster.UserName) - } - } - assert.Equal(t, body, apiComment.Body) } } @@ -257,22 +265,3 @@ func TestAPIDeleteComment(t *testing.T) { unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID}) } - -func TestAPIListIssueTimeline(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // load comment - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - // make request - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/timeline", repoOwner.Name, repo.Name, issue.Index) - resp := MakeRequest(t, req, http.StatusOK) - - // check if lens of list returned by API and - // lists extracted directly from DB are the same - comments := DecodeJSON(t, resp, []*api.TimelineComment{}) - expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}) - assert.Len(t, comments, expectedCount) -} diff --git a/tests/integration/api_gitignore_templates_test.go b/tests/integration/api_gitignore_templates_test.go index 50e224bf67f..a639c3a4250 100644 --- a/tests/integration/api_gitignore_templates_test.go +++ b/tests/integration/api_gitignore_templates_test.go @@ -15,37 +15,37 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIListGitignoresTemplates(t *testing.T) { +func TestAPIGitignoreTemplates(t *testing.T) { defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/gitignore/templates") - resp := MakeRequest(t, req, http.StatusOK) + t.Run("List", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/gitignore/templates") + resp := MakeRequest(t, req, http.StatusOK) - templateList := DecodeJSON(t, resp, []string{}) // this is a very long list - assert.Contains(t, templateList, "C++") - assert.Contains(t, templateList, "Go") -} - -func TestAPIGetGitignoreTemplateInfo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // If Gitea has for some reason no Gitignore templates, we need to skip this test - if len(repo_module.Gitignores) == 0 { - return - } - - // Use the first template for the test - templateName := repo_module.Gitignores[0] - - urlStr := "/api/v1/gitignore/templates/" + templateName - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - templateInfo := DecodeJSON(t, resp, &api.GitignoreTemplateInfo{}) - - // We get the text of the template here - text, _ := options.Gitignore(templateName) - - assert.Equal(t, templateInfo.Name, templateName) - assert.Equal(t, templateInfo.Source, string(text)) + templateList := DecodeJSON(t, resp, []string{}) // this is a very long list + assert.Contains(t, templateList, "C++") + assert.Contains(t, templateList, "Go") + }) + + t.Run("Info", func(t *testing.T) { + // If Gitea has for some reason no Gitignore templates, we need to skip this test + if len(repo_module.Gitignores) == 0 { + return + } + + // Use the first template for the test + templateName := repo_module.Gitignores[0] + + urlStr := "/api/v1/gitignore/templates/" + templateName + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + templateInfo := DecodeJSON(t, resp, &api.GitignoreTemplateInfo{}) + + // We get the text of the template here + text, _ := options.Gitignore(templateName) + + assert.Equal(t, templateInfo.Name, templateName) + assert.Equal(t, templateInfo.Source, string(text)) + }) } diff --git a/tests/integration/api_gpg_keys_test.go b/tests/integration/api_gpg_keys_test.go index a2a19d58888..0240414bd33 100644 --- a/tests/integration/api_gpg_keys_test.go +++ b/tests/integration/api_gpg_keys_test.go @@ -47,31 +47,23 @@ func TestGPGKeys(t *testing.T) { for _, tc := range tt { // Basic test on result code t.Run(tc.name, func(t *testing.T) { - t.Run("ViewOwnGPGKeys", func(t *testing.T) { - testViewOwnGPGKeys(t, tc.makeRequest, tc.token, tc.results[0]) - }) - t.Run("ViewGPGKeys", func(t *testing.T) { - testViewGPGKeys(t, tc.makeRequest, tc.token, tc.results[1]) - }) - t.Run("GetGPGKey", func(t *testing.T) { - testGetGPGKey(t, tc.makeRequest, tc.token, tc.results[2]) - }) - t.Run("DeleteGPGKey", func(t *testing.T) { - testDeleteGPGKey(t, tc.makeRequest, tc.token, tc.results[3]) - }) - - t.Run("CreateInvalidGPGKey", func(t *testing.T) { - testCreateInvalidGPGKey(t, tc.makeRequest, tc.token, tc.results[4]) - }) - t.Run("CreateNoneRegisteredEmailGPGKey", func(t *testing.T) { - testCreateNoneRegisteredEmailGPGKey(t, tc.makeRequest, tc.token, tc.results[5]) - }) - t.Run("CreateValidGPGKey", func(t *testing.T) { - testCreateValidGPGKey(t, tc.makeRequest, tc.token, tc.results[6]) - }) - t.Run("CreateValidSecondaryEmailGPGKeyNotActivated", func(t *testing.T) { - testCreateValidSecondaryEmailGPGKey(t, tc.makeRequest, tc.token, tc.results[7]) - }) + for index, operation := range []struct { + name string + run func(*testing.T, makeRequestFunc, string, int) + }{ + {"ViewOwnGPGKeys", testViewOwnGPGKeys}, + {"ViewGPGKeys", testViewGPGKeys}, + {"GetGPGKey", testGetGPGKey}, + {"DeleteGPGKey", testDeleteGPGKey}, + {"CreateInvalidGPGKey", testCreateInvalidGPGKey}, + {"CreateNoneRegisteredEmailGPGKey", testCreateNoneRegisteredEmailGPGKey}, + {"CreateValidGPGKey", testCreateValidGPGKey}, + {"CreateValidSecondaryEmailGPGKeyNotActivated", testCreateValidSecondaryEmailGPGKey}, + } { + t.Run(operation.name, func(t *testing.T) { + operation.run(t, tc.makeRequest, tc.token, tc.results[index]) + }) + } }) } @@ -112,29 +104,22 @@ func TestGPGKeys(t *testing.T) { // Check state after basic add t.Run("CheckCommits", func(t *testing.T) { - t.Run("NotSigned", func(t *testing.T) { - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/not-signed"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - branch := DecodeJSON(t, resp, &api.Branch{}) - assert.False(t, branch.Commit.Verification.Verified) - }) - - t.Run("SignedWithNotValidatedEmail", func(t *testing.T) { - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/good-sign-not-yet-validated"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - branch := DecodeJSON(t, resp, &api.Branch{}) - assert.False(t, branch.Commit.Verification.Verified) - }) - - t.Run("SignedWithValidEmail", func(t *testing.T) { - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/good-sign"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - branch := DecodeJSON(t, resp, &api.Branch{}) - assert.True(t, branch.Commit.Verification.Verified) - }) + for _, tc := range []struct { + name, branch string + assert assert.BoolAssertionFunc + }{ + {"NotSigned", "not-signed", assert.False}, + {"SignedWithNotValidatedEmail", "good-sign-not-yet-validated", assert.False}, + {"SignedWithValidEmail", "good-sign", assert.True}, + } { + t.Run(tc.name, func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/"+tc.branch). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + branch := DecodeJSON(t, resp, &api.Branch{}) + tc.assert(t, branch.Commit.Verification.Verified) + }) + } }) } diff --git a/tests/integration/api_httpsig_test.go b/tests/integration/api_httpsig_test.go index d100d0728ed..3056d936462 100644 --- a/tests/integration/api_httpsig_test.go +++ b/tests/integration/api_httpsig_test.go @@ -51,93 +51,92 @@ fhTNAzWwZoQ91aHdAAAAFHUwMDIyMTQ2QGljdHMtcC1ueC03AQIDBAUG httpsigCertificate = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgiR7SU8gmZLhopx4Y03nOXVuAb+4fyMcJYjMGcE1Z2oEAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqdAAAAAAAAAAEAAAABAAAABXVzZXIxAAAACQAAAAV1c2VyMQAAAABimoIOAAAAAMCWkRMAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEAm+AwtXTBZyeqV1qOxjMU3Ibc5iR2M3zerGfRQDxUeIozC3xpIvqJbzjDuRapdf8hpxn2xC0GtUusuLIUr4/+Svs1BUnJhF2H9xnK/O0aopS5MpNekUvnBzQdbvO8Ux2xE2mt58giXhkEaXeCEODSqG++OZsA2e40AR/AGRJ4OdDofMvH4vLJAQQc2mKdYpYL8xu+NC+7nsenx1etpsqtEl3gmvqCVI6t9uhVPMvlbGt9h/AN3u7ToF2T3bdk1TZbcdkvR9ljvETIuy32ksAETX8tc7vm30edK+nn/GMeWCgjM+MFm9Uh1NRkvNNJozo5SJy0DkWETTJUsEdfry5VQ3IjqhWqQ0m4/mDlTmsEdEdWqpUiqWZLd9w7jgT8fanuglZyIu2fj8fyqjPjiws5S2P0Uvi28UKQ1nH01UYj/kuakU3BNzN1IqDf3tARP9fjKV/dCBqb1ZAOtyC2GyhGuGzNwEi+woUwq+sTeV0/hqVSb3hSitXHzcfRMRyOK82BAAABlAAAAAxyc2Etc2hhMi01MTIAAAGAMBfgZFvz4BdxriGKYd6eRhMo6hf+I8S9uzNRsflJXHuA+HR9ExIm/Q9JjKmfThQzNyGGBOBILaDU205SAJuG+kk3SieSQDd75ZQd8YmNlCc+516AriOsTiyVCupnf3I2euTjMZqEZbJcBbkBljppTOWQVN7xxE8QakDfGhg0+RjJE9wYOTmkKpDBfII5Nw8V5DoOD7kNEpXYqHdy/8lVxpqUYNIP1J0dNP4f6qBcZcM1PDA12q8zwIGqSNNjf2UXY/Nr8nv9CnK4fB8NDOPKTBa4cm48BGbvM/X0l6dYKswuZ9Np8lw+y6+GxTgznGCrkzMmuEV4FzSq4xHp41H2L2MTwUkwYaeyG1VP6aWkvn6zPkSxaaJDfQX7CAFe17IhIGXR0UPLjKjh35nDLzMWb/W6/W1lK9YkZNHXSf7Z9m9MUAZN7yQgOggGsuYEW4imZxvZizMd+fdDu9mbhr0FDis89I7MSJDnyYRE9FXS7p3QpppBwGcss/9yV3JV3Bjc` ) -func TestHTTPSigPubKey(t *testing.T) { - // Add our public key to user1 - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.SSH.MinimumKeySizeCheck, false)() - session := loginUser(t, "user1") - token := url.QueryEscape(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)) - keyType := "ssh-rsa" - keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqd" - rawKeyBody := api.CreateKeyOption{ - Title: "test-key", - Key: keyType + " " + keyContent, - } - req := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", rawKeyBody). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - // parse our private key and create the httpsig request - sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) - keyID := ssh.FingerprintSHA256(sshSigner.PublicKey()) - - // create the request - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) - req = NewRequest(t, "GET", "/api/v1/admin/users"). - AddTokenAuth(token) - - signer, _, err := httpsig.NewSSHSigner(sshSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)"}, httpsig.Signature, 10) - if err != nil { - t.Fatal(err) - } - - // sign the request - err = signer.SignRequest(keyID, req.Request, nil) - if err != nil { - t.Fatal(err) - } - - // make the request - MakeRequest(t, req, http.StatusOK) -} - -func TestHTTPSigCert(t *testing.T) { - // Add our public key to user1 +func TestHTTPSig(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") - req := NewRequestWithValues(t, "POST", "/user/settings/keys", map[string]string{ - "content": "user1", - "title": "principal", - "type": "principal", + t.Run("pub-key", func(t *testing.T) { + // Add our public key to user1 + defer test.MockVariableValue(&setting.SSH.MinimumKeySizeCheck, false)() + token := url.QueryEscape(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)) + keyType := "ssh-rsa" + keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqd" + rawKeyBody := api.CreateKeyOption{ + Title: "test-key", + Key: keyType + " " + keyContent, + } + req := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", rawKeyBody). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + // parse our private key and create the httpsig request + sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) + keyID := ssh.FingerprintSHA256(sshSigner.PublicKey()) + + // create the request + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) + req = NewRequest(t, "GET", "/api/v1/admin/users"). + AddTokenAuth(token) + + signer, _, err := httpsig.NewSSHSigner(sshSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)"}, httpsig.Signature, 10) + if err != nil { + t.Fatal(err) + } + + // sign the request + err = signer.SignRequest(keyID, req.Request, nil) + if err != nil { + t.Fatal(err) + } + + // make the request + MakeRequest(t, req, http.StatusOK) }) + t.Run("cert", func(t *testing.T) { + // Add our public key to user1 + req := NewRequestWithValues(t, "POST", "/user/settings/keys", map[string]string{ + "content": "user1", + "title": "principal", + "type": "principal", + }) - session.MakeRequest(t, req, http.StatusSeeOther) - pkcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(httpsigCertificate)) - if err != nil { - t.Fatal(err) - } + session.MakeRequest(t, req, http.StatusSeeOther) + pkcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(httpsigCertificate)) + if err != nil { + t.Fatal(err) + } - // parse our private key and create the httpsig request - sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) - keyID := "gitea" + // parse our private key and create the httpsig request + sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) + keyID := "gitea" - // create our certificate signer using the ssh signer and our certificate - cert, ok := pkcert.(*ssh.Certificate) - require.True(t, ok) - certSigner, err := ssh.NewCertSigner(cert, sshSigner) - if err != nil { - t.Fatal(err) - } + // create our certificate signer using the ssh signer and our certificate + cert, ok := pkcert.(*ssh.Certificate) + require.True(t, ok) + certSigner, err := ssh.NewCertSigner(cert, sshSigner) + if err != nil { + t.Fatal(err) + } - // create the request - req = NewRequest(t, "GET", "/api/v1/admin/users") + // create the request + req = NewRequest(t, "GET", "/api/v1/admin/users") - // add our cert to the request - certString := base64.RawStdEncoding.EncodeToString(cert.Marshal()) - req.SetHeader("x-ssh-certificate", certString) + // add our cert to the request + certString := base64.RawStdEncoding.EncodeToString(cert.Marshal()) + req.SetHeader("x-ssh-certificate", certString) - signer, _, err := httpsig.NewSSHSigner(certSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)", "x-ssh-certificate"}, httpsig.Signature, 10) - if err != nil { - t.Fatal(err) - } + signer, _, err := httpsig.NewSSHSigner(certSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)", "x-ssh-certificate"}, httpsig.Signature, 10) + if err != nil { + t.Fatal(err) + } - // sign the request - err = signer.SignRequest(keyID, req.Request, nil) - if err != nil { - t.Fatal(err) - } + // sign the request + err = signer.SignRequest(keyID, req.Request, nil) + if err != nil { + t.Fatal(err) + } - // make the request - MakeRequest(t, req, http.StatusOK) + // make the request + MakeRequest(t, req, http.StatusOK) + }) } diff --git a/tests/integration/api_issue_attachment_test.go b/tests/integration/api_issue_attachment_test.go index 9656946eefc..77d39894d99 100644 --- a/tests/integration/api_issue_attachment_test.go +++ b/tests/integration/api_issue_attachment_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIGetIssueAttachment(t *testing.T) { +func TestAPIIssueAttachment(t *testing.T) { defer tests.PrepareTestEnv(t)() attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1}) @@ -30,38 +30,60 @@ func TestAPIGetIssueAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID)). - AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusOK) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + for _, operation := range []string{"get", "list", "edit-unallowed", "edit"} { + t.Run(operation, func(t *testing.T) { + token := readToken + if operation == "edit-unallowed" || operation == "edit" { + token = writeToken + } - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID}) + switch operation { + case "get": + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID)). + AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusOK) + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - commentAttachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 3, RepoID: repo.ID}) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: commentAttachment.IssueID}).Index, commentAttachment.ID)). - AddTokenAuth(token) - session.MakeRequest(t, req, http.StatusNotFound) -} + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID}) -func TestAPIListIssueAttachments(t *testing.T) { - defer tests.PrepareTestEnv(t)() + commentAttachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 3, RepoID: repo.ID}) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: commentAttachment.IssueID}).Index, commentAttachment.ID)). + AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusNotFound) + case "list": + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index)). + AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusOK) + apiAttachment := DecodeJSON(t, resp, []api.Attachment{}) - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: attachment.IssueID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment[0].ID, IssueID: issue.ID}) + case "edit-unallowed": + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: attachment.IssueID}) + filename := "file.bad" + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID) + req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ + "name": filename, + }).AddTokenAuth(token) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + case "edit": + const newAttachmentName = "hello_world.txt" - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index)). - AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusOK) - apiAttachment := DecodeJSON(t, resp, []api.Attachment{}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", + repoOwner.Name, repo.Name, issue.Index, attachment.ID) + req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ + "name": newAttachmentName, + }).AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusCreated) + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment[0].ID, IssueID: issue.ID}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID, Name: apiAttachment.Name}) + } + }) + } } func TestAPICreateIssueAttachment(t *testing.T) { @@ -74,95 +96,38 @@ func TestAPICreateIssueAttachment(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - body := &bytes.Buffer{} + for _, testCase := range []struct { + filename string + status int + }{ + {"file.bad", http.StatusUnprocessableEntity}, + {"image.png", http.StatusCreated}, + } { + t.Run(testCase.filename, func(t *testing.T) { + body := &bytes.Buffer{} - // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", "image.png") - assert.NoError(t, err) - _, err = part.Write(testGeneratePngBytes()) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) + // Setup multi-part + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("attachment", testCase.filename) + assert.NoError(t, err) + if testCase.status == http.StatusCreated { + _, err = part.Write(testGeneratePngBytes()) + assert.NoError(t, err) + } + err = writer.Close() + assert.NoError(t, err) - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). - AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) - resp := session.MakeRequest(t, req, http.StatusCreated) + req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). + AddTokenAuth(token) + req.Header.Add("Content-Type", writer.FormDataContentType()) + resp := session.MakeRequest(t, req, testCase.status) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID}) -} - -func TestAPICreateIssueAttachmentWithUnallowedFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - filename := "file.bad" - body := &bytes.Buffer{} - - // Setup multi-part. - writer := multipart.NewWriter(body) - _, err := writer.CreateFormFile("attachment", filename) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). - AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) -} - -func TestAPIEditIssueAttachment(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - const newAttachmentName = "hello_world.txt" - - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: attachment.IssueID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", - repoOwner.Name, repo.Name, issue.Index, attachment.ID) - req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - "name": newAttachmentName, - }).AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusCreated) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID, Name: apiAttachment.Name}) -} - -func TestAPIEditIssueAttachmentWithUnallowedFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: attachment.IssueID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - filename := "file.bad" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID) - req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - "name": filename, - }).AddTokenAuth(token) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) + if testCase.status == http.StatusCreated { + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID}) + } + }) + } } func TestAPIDeleteIssueAttachment(t *testing.T) { diff --git a/tests/integration/api_issue_config_test.go b/tests/integration/api_issue_config_test.go index 88dcd539203..426b7ef697b 100644 --- a/tests/integration/api_issue_config_test.go +++ b/tests/integration/api_issue_config_test.go @@ -50,6 +50,51 @@ func TestAPIRepoGetIssueConfig(t *testing.T) { assert.Empty(t, issueConfig.ContactLinks) }) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) + + t.Run("Validate/Valid", func(t *testing.T) { + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + issueConfigValidation := DecodeJSON(t, resp, &api.IssueConfigValidation{}) + + assert.True(t, issueConfigValidation.Valid) + assert.Empty(t, issueConfigValidation.Message) + }) + + t.Run("Paths", func(t *testing.T) { + templateConfigCandidates := []string{ + ".gitea/ISSUE_TEMPLATE/config", + ".gitea/issue_template/config", + ".github/ISSUE_TEMPLATE/config", + ".github/issue_template/config", + } + + for _, candidate := range templateConfigCandidates { + for _, extension := range []string{".yaml", ".yml"} { + fullPath := candidate + extension + t.Run(fullPath, func(t *testing.T) { + configMap := make(map[string]any) + configMap["blank_issues_enabled"] = false + + configData, err := yaml.Marshal(configMap) + assert.NoError(t, err) + + _, err = createFile(owner, repo, fullPath, string(configData)) + assert.NoError(t, err) + + issueConfig := getIssueConfig(t, owner.Name, repo.Name) + + assert.False(t, issueConfig.BlankIssuesEnabled) + assert.Empty(t, issueConfig.ContactLinks) + + _, err = deleteFileInBranch(owner, repo, fullPath, repo.DefaultBranch) + assert.NoError(t, err) + }) + } + } + }) + t.Run("DisableBlankIssues", func(t *testing.T) { config := make(map[string]any) config["blank_issues_enabled"] = false @@ -62,107 +107,37 @@ func TestAPIRepoGetIssueConfig(t *testing.T) { assert.Empty(t, issueConfig.ContactLinks) }) - t.Run("ContactLinks", func(t *testing.T) { - contactLink := make(map[string]string) - contactLink["name"] = "TestName" - contactLink["url"] = "https://example.com" - contactLink["about"] = "TestAbout" + for _, name := range []string{"ContactLinks", "Full"} { + t.Run(name, func(t *testing.T) { + contactLink := make(map[string]string) + contactLink["name"] = "TestName" + contactLink["url"] = "https://example.com" + contactLink["about"] = "TestAbout" - config := make(map[string]any) - config["contact_links"] = []map[string]string{contactLink} + config := make(map[string]any) + if name == "Full" { + config["blank_issues_enabled"] = false + } + config["contact_links"] = []map[string]string{contactLink} - createIssueConfig(t, owner, repo, config) + createIssueConfig(t, owner, repo, config) - issueConfig := getIssueConfig(t, owner.Name, repo.Name) - - assert.True(t, issueConfig.BlankIssuesEnabled) - assert.Len(t, issueConfig.ContactLinks, 1) - - assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) - assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) - assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) - }) - - t.Run("Full", func(t *testing.T) { - contactLink := make(map[string]string) - contactLink["name"] = "TestName" - contactLink["url"] = "https://example.com" - contactLink["about"] = "TestAbout" - - config := make(map[string]any) - config["blank_issues_enabled"] = false - config["contact_links"] = []map[string]string{contactLink} - - createIssueConfig(t, owner, repo, config) - - issueConfig := getIssueConfig(t, owner.Name, repo.Name) - - assert.False(t, issueConfig.BlankIssuesEnabled) - assert.Len(t, issueConfig.ContactLinks, 1) - - assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) - assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) - assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) - }) -} - -func TestAPIRepoIssueConfigPaths(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - templateConfigCandidates := []string{ - ".gitea/ISSUE_TEMPLATE/config", - ".gitea/issue_template/config", - ".github/ISSUE_TEMPLATE/config", - ".github/issue_template/config", - } - - for _, candidate := range templateConfigCandidates { - for _, extension := range []string{".yaml", ".yml"} { - fullPath := candidate + extension - t.Run(fullPath, func(t *testing.T) { - configMap := make(map[string]any) - configMap["blank_issues_enabled"] = false - - configData, err := yaml.Marshal(configMap) - assert.NoError(t, err) - - _, err = createFile(owner, repo, fullPath, string(configData)) - assert.NoError(t, err) - - issueConfig := getIssueConfig(t, owner.Name, repo.Name) + issueConfig := getIssueConfig(t, owner.Name, repo.Name) + if name == "Full" { assert.False(t, issueConfig.BlankIssuesEnabled) - assert.Empty(t, issueConfig.ContactLinks) + } else { + assert.True(t, issueConfig.BlankIssuesEnabled) + } + assert.Len(t, issueConfig.ContactLinks, 1) - _, err = deleteFileInBranch(owner, repo, fullPath, repo.DefaultBranch) - assert.NoError(t, err) - }) - } + assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) + assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) + assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) + }) } -} -func TestAPIRepoValidateIssueConfig(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) - - t.Run("Valid", func(t *testing.T) { - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - issueConfigValidation := DecodeJSON(t, resp, &api.IssueConfigValidation{}) - - assert.True(t, issueConfigValidation.Valid) - assert.Empty(t, issueConfigValidation.Message) - }) - - t.Run("Invalid", func(t *testing.T) { + t.Run("Validate/Invalid", func(t *testing.T) { config := make(map[string]any) config["blank_issues_enabled"] = "Test" @@ -176,20 +151,18 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) { assert.False(t, issueConfigValidation.Valid) assert.NotEmpty(t, issueConfigValidation.Message) }) -} - -func TestAPIRepoIssueConfigRequiresCodeUnit(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) - - for _, path := range []string{ - fmt.Sprintf("/api/v1/repos/%s/issue_config", repo.FullName()), - fmt.Sprintf("/api/v1/repos/%s/issue_config/validate", repo.FullName()), - } { - req := NewRequest(t, "GET", path).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - } + + t.Run("RequiresCodeUnit", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) + + for _, path := range []string{ + fmt.Sprintf("/api/v1/repos/%s/issue_config", repo.FullName()), + fmt.Sprintf("/api/v1/repos/%s/issue_config/validate", repo.FullName()), + } { + req := NewRequest(t, "GET", path).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + } + }) } diff --git a/tests/integration/api_issue_dependency_test.go b/tests/integration/api_issue_dependency_test.go index 5685c217baf..64b9bf5e490 100644 --- a/tests/integration/api_issue_dependency_test.go +++ b/tests/integration/api_issue_dependency_test.go @@ -37,155 +37,105 @@ func enableRepoDependencies(t *testing.T, repoID int64) { func TestAPICreateIssueDependencyCrossRepoPermission(t *testing.T) { defer tests.PrepareTestEnv(t)() - targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) - dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, dependencyRepo.IsPrivate) - dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) + for _, method := range []string{"POST", "DELETE"} { + t.Run(method, func(t *testing.T) { + if method == "DELETE" { + assert.NoError(t, unittest.LoadFixtures()) + } - enableRepoDependencies(t, targetIssue.RepoID) - enableRepoDependencies(t, dependencyRepo.ID) + targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) + dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, dependencyRepo.IsPrivate) + dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) - // remove user 40 access from target repository - _, err := db.DeleteByID[access_model.Access](t.Context(), 30) - assert.NoError(t, err) + enableRepoDependencies(t, targetIssue.RepoID) + enableRepoDependencies(t, dependencyRepo.ID) - url := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", "user2", "repo1", targetIssue.Index) - dependencyMeta := &api.IssueMeta{ - Owner: "org3", - Name: "repo3", - Index: dependencyIssue.Index, + if method == "DELETE" { + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) + } + + // remove user 40 access from target repository + _, err := db.DeleteByID[access_model.Access](t.Context(), 30) + assert.NoError(t, err) + + url := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", "user2", "repo1", targetIssue.Index) + dependencyMeta := &api.IssueMeta{ + Owner: "org3", + Name: "repo3", + Index: dependencyIssue.Index, + } + + user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + // user40 has no access to both target issue and dependency issue + writerToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue) + checkDependency := func(expectedStatus int, exists bool) { + req := NewRequestWithJSON(t, method, url, dependencyMeta). + AddTokenAuth(writerToken) + MakeRequest(t, req, expectedStatus) + if exists { + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) + } else { + unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) + } + } + checkDependency(http.StatusNotFound, method == "DELETE") + + // add user40 as a collaborator to dependency repository with read permission + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) + + // try again after getting read permission to dependency repository + checkDependency(http.StatusNotFound, method == "DELETE") + + // add user40 as a collaborator to target repository with write permission + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) + + checkDependency(http.StatusCreated, method == "POST") + }) } - user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - // user40 has no access to both target issue and dependency issue - writerToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue) - req := NewRequestWithJSON(t, "POST", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusNotFound) - unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) + t.Run("WebDelete", func(t *testing.T) { + assert.NoError(t, unittest.LoadFixtures()) - // add user40 as a collaborator to dependency repository with read permission - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) + targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) + dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, dependencyRepo.IsPrivate) + dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) - // try again after getting read permission to dependency repository - req = NewRequestWithJSON(t, "POST", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusNotFound) - unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) + enableRepoDependencies(t, targetIssue.RepoID) + enableRepoDependencies(t, dependencyRepo.ID) - // add user40 as a collaborator to target repository with write permission - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) - req = NewRequestWithJSON(t, "POST", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusCreated) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, + user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) + + session := loginUser(t, user40.Name) + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/issues/%d/dependency/delete", targetIssue.Index), map[string]string{ + "removeDependencyID": strconv.FormatInt(dependencyIssue.ID, 10), + "dependencyType": "blockedBy", + }) + resp := session.MakeRequest(t, req, http.StatusBadRequest) + assert.Equal(t, "Permission denied.", test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{IssueID: targetIssue.ID, DependencyID: dependencyIssue.ID}) + + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) + + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/issues/%d/dependency/delete", targetIssue.Index), map[string]string{ + "removeDependencyID": strconv.FormatInt(dependencyIssue.ID, 10), + "dependencyType": "blockedBy", + }) + session.MakeRequest(t, req, http.StatusOK) + unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{IssueID: targetIssue.ID, DependencyID: dependencyIssue.ID}) }) } - -func TestAPIDeleteIssueDependencyCrossRepoPermission(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) - dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, dependencyRepo.IsPrivate) - dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) - - enableRepoDependencies(t, targetIssue.RepoID) - enableRepoDependencies(t, dependencyRepo.ID) - - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) - - // remove user 40 access from target repository - _, err := db.DeleteByID[access_model.Access](t.Context(), 30) - assert.NoError(t, err) - - url := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", "user2", "repo1", targetIssue.Index) - dependencyMeta := &api.IssueMeta{ - Owner: "org3", - Name: "repo3", - Index: dependencyIssue.Index, - } - - user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - // user40 has no access to both target issue and dependency issue - writerToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue) - req := NewRequestWithJSON(t, "DELETE", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusNotFound) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) - - // add user40 as a collaborator to dependency repository with read permission - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) - - // try again after getting read permission to dependency repository - req = NewRequestWithJSON(t, "DELETE", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusNotFound) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) - - // add user40 as a collaborator to target repository with write permission - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) - - req = NewRequestWithJSON(t, "DELETE", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusCreated) - unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) -} - -func TestWebDeleteIssueDependencyCrossRepoPermission(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) - dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, dependencyRepo.IsPrivate) - dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) - - enableRepoDependencies(t, targetIssue.RepoID) - enableRepoDependencies(t, dependencyRepo.ID) - - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) - - user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) - - session := loginUser(t, user40.Name) - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/issues/%d/dependency/delete", targetIssue.Index), map[string]string{ - "removeDependencyID": strconv.FormatInt(dependencyIssue.ID, 10), - "dependencyType": "blockedBy", - }) - resp := session.MakeRequest(t, req, http.StatusBadRequest) - assert.Equal(t, "Permission denied.", test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{IssueID: targetIssue.ID, DependencyID: dependencyIssue.ID}) - - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) - - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/issues/%d/dependency/delete", targetIssue.Index), map[string]string{ - "removeDependencyID": strconv.FormatInt(dependencyIssue.ID, 10), - "dependencyType": "blockedBy", - }) - session.MakeRequest(t, req, http.StatusOK) - unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{IssueID: targetIssue.ID, DependencyID: dependencyIssue.ID}) -} diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go index 35a2873ce78..1ced709204f 100644 --- a/tests/integration/api_issue_label_test.go +++ b/tests/integration/api_issue_label_test.go @@ -22,71 +22,121 @@ import ( func TestAPIModifyLabels(t *testing.T) { assert.NoError(t, unittest.LoadFixtures()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner.Name, repo.Name) + t.Run("AddIssueLabelsWithLabelNames", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6, RepoID: repo.ID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + repoLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10, RepoID: repo.ID}) + orgLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4, OrgID: owner.ID}) - // CreateLabel - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "TestL 1", - Color: "abcdef", - Description: "test label", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiLabel := DecodeJSON(t, resp, &api.Label{}) - dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, RepoID: repo.ID}) - assert.Equal(t, dbLabel.Name, apiLabel.Name) - assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + user1Session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteIssue) - req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "TestL 2", - Color: "#123456", - Description: "jet another test label", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "WrongTestL", - Color: "#12345g", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + // add the org label and the repo label to the issue + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner.Name, repo.Name, issue.Index) + req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ + Labels: []any{repoLabel.Name, orgLabel.Name}, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + apiLabels := DecodeJSON(t, resp, []*api.Label{}) + assert.Len(t, apiLabels, unittest.GetCount(t, &issues_model.IssueLabel{IssueID: issue.ID})) + var apiLabelNames []string + for _, label := range apiLabels { + apiLabelNames = append(apiLabelNames, label.Name) + } + assert.ElementsMatch(t, apiLabelNames, []string{repoLabel.Name, orgLabel.Name}) - // ListLabels - req = NewRequest(t, "GET", urlStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabels := DecodeJSON(t, resp, []*api.Label{}) - assert.Len(t, apiLabels, 2) + // delete labels + req = NewRequest(t, "DELETE", urlStr).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) - // GetLabel - singleURLStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner.Name, repo.Name, dbLabel.ID) - req = NewRequest(t, "GET", singleURLStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabel = DecodeJSON(t, resp, &api.Label{}) - assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + for _, testCase := range []struct { + name string + repoID int64 + labelCount int + }{ + {"Repository", 2, 2}, + {"Organization", 3, 4}, + } { + t.Run(testCase.name, func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: testCase.repoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + username := owner.Name + scopes := []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteIssue} + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner.Name, repo.Name) + labelFilter := &issues_model.Label{RepoID: repo.ID} + if testCase.name == "Organization" { + username = "user1" + scopes = []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization} + urlStr = fmt.Sprintf("/api/v1/orgs/%s/labels", owner.Name) + labelFilter = &issues_model.Label{OrgID: owner.ID} + } + session := loginUser(t, username) + token := getTokenForLoggedInUser(t, session, scopes...) - // EditLabel - newName := "LabelNewName" - newColor := "09876a" - newColorWrong := "09g76a" - req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ - Name: &newName, - Color: &newColor, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabel = DecodeJSON(t, resp, &api.Label{}) - assert.Equal(t, newColor, apiLabel.Color) - req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ - Color: &newColorWrong, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + // CreateLabel + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "TestL 1", + Color: "abcdef", + Description: "test label", + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + apiLabel := DecodeJSON(t, resp, &api.Label{}) + labelFilter.ID = apiLabel.ID + dbLabel := unittest.AssertExistsAndLoadBean(t, labelFilter) + assert.Equal(t, dbLabel.Name, apiLabel.Name) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) - // DeleteLabel - req = NewRequest(t, "DELETE", singleURLStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "TestL 2", + Color: "#123456", + Description: "jet another test label", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "WrongTestL", + Color: "#12345g", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // ListLabels + req = NewRequest(t, "GET", urlStr). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiLabels := DecodeJSON(t, resp, []*api.Label{}) + assert.Len(t, apiLabels, testCase.labelCount) + + // GetLabel + singleURLStr := fmt.Sprintf("%s/%d", urlStr, dbLabel.ID) + req = NewRequest(t, "GET", singleURLStr). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiLabel = DecodeJSON(t, resp, &api.Label{}) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + + // EditLabel + newName := "LabelNewName" + newColor := "09876a" + newColorWrong := "09g76a" + req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ + Name: &newName, + Color: &newColor, + }).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiLabel = DecodeJSON(t, resp, &api.Label{}) + assert.Equal(t, newColor, apiLabel.Color) + req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ + Color: &newColorWrong, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // DeleteLabel + req = NewRequest(t, "DELETE", singleURLStr). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) + } } func TestAPIAddIssueLabels(t *testing.T) { @@ -144,37 +194,6 @@ func TestAPIDeleteIssueLabelCrossRepo(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) } -func TestAPIAddIssueLabelsWithLabelNames(t *testing.T) { - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6, RepoID: repo.ID}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - repoLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10, RepoID: repo.ID}) - orgLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4, OrgID: owner.ID}) - - user1Session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteIssue) - - // add the org label and the repo label to the issue - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner.Name, repo.Name, issue.Index) - req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ - Labels: []any{repoLabel.Name, orgLabel.Name}, - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - apiLabels := DecodeJSON(t, resp, []*api.Label{}) - assert.Len(t, apiLabels, unittest.GetCount(t, &issues_model.IssueLabel{IssueID: issue.ID})) - var apiLabelNames []string - for _, label := range apiLabels { - apiLabelNames = append(apiLabelNames, label.Name) - } - assert.ElementsMatch(t, apiLabelNames, []string{repoLabel.Name, orgLabel.Name}) - - // delete labels - req = NewRequest(t, "DELETE", urlStr).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) -} - func TestAPIAddIssueLabelsWithSharedRepoAndOrgLabelName(t *testing.T) { assert.NoError(t, unittest.LoadFixtures()) @@ -255,74 +274,3 @@ func TestAPIReplaceIssueLabelsWithLabelNames(t *testing.T) { assert.Equal(t, label.Name, apiLabels[0].Name) } } - -func TestAPIModifyOrgLabels(t *testing.T) { - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - user := "user1" - session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization) - urlStr := fmt.Sprintf("/api/v1/orgs/%s/labels", owner.Name) - - // CreateLabel - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "TestL 1", - Color: "abcdef", - Description: "test label", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiLabel := DecodeJSON(t, resp, &api.Label{}) - dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, OrgID: owner.ID}) - assert.Equal(t, dbLabel.Name, apiLabel.Name) - assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) - - req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "TestL 2", - Color: "#123456", - Description: "jet another test label", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "WrongTestL", - Color: "#12345g", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - - // ListLabels - req = NewRequest(t, "GET", urlStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabels := DecodeJSON(t, resp, []*api.Label{}) - assert.Len(t, apiLabels, 4) - - // GetLabel - singleURLStr := fmt.Sprintf("/api/v1/orgs/%s/labels/%d", owner.Name, dbLabel.ID) - req = NewRequest(t, "GET", singleURLStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabel = DecodeJSON(t, resp, &api.Label{}) - assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) - - // EditLabel - newName := "LabelNewName" - newColor := "09876a" - newColorWrong := "09g76a" - req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ - Name: &newName, - Color: &newColor, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabel = DecodeJSON(t, resp, &api.Label{}) - assert.Equal(t, newColor, apiLabel.Color) - req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ - Color: &newColorWrong, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - - // DeleteLabel - req = NewRequest(t, "DELETE", singleURLStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) -} diff --git a/tests/integration/api_issue_pin_test.go b/tests/integration/api_issue_pin_test.go index 3b10ee4087b..b25eb0d0355 100644 --- a/tests/integration/api_issue_pin_test.go +++ b/tests/integration/api_issue_pin_test.go @@ -19,34 +19,31 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIPinIssue(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - // Pin the Issue - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Check if the Issue is pinned - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)) - resp := MakeRequest(t, req, http.StatusOK) - issueAPI := DecodeJSON(t, resp, &api.Issue{}) - assert.Equal(t, 1, issueAPI.PinOrder) -} - func TestAPIUnpinIssue(t *testing.T) { defer tests.PrepareTestEnv(t)() - assert.NoError(t, unittest.LoadFixtures()) + t.Run("ListPinnedPullRequests", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/pinned", repo.OwnerName, repo.Name)) + resp := MakeRequest(t, req, http.StatusOK) + prList := DecodeJSON(t, resp, []api.PullRequest{}) + + assert.Empty(t, prList) + }) + + t.Run("NewPinAllowed", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/new_pin_allowed", owner.Name, repo.Name)) + resp := MakeRequest(t, req, http.StatusOK) + + newPinsAllowed := DecodeJSON(t, resp, &api.NewIssuePinsAllowed{}) + + assert.True(t, newPinsAllowed.Issues) + assert.True(t, newPinsAllowed.PullRequests) + }) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) @@ -66,6 +63,14 @@ func TestAPIUnpinIssue(t *testing.T) { issueAPI := DecodeJSON(t, resp, &api.Issue{}) assert.Equal(t, 1, issueAPI.PinOrder) + // Check if the Issue is in the List + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/pinned", repo.OwnerName, repo.Name)) + resp = MakeRequest(t, req, http.StatusOK) + issueList := DecodeJSON(t, resp, []api.Issue{}) + + assert.Len(t, issueList, 1) + assert.Equal(t, issue.ID, issueList[0].ID) + // Unpin the Issue req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). AddTokenAuth(token) @@ -81,8 +86,6 @@ func TestAPIUnpinIssue(t *testing.T) { func TestAPIMoveIssuePin(t *testing.T) { defer tests.PrepareTestEnv(t)() - assert.NoError(t, unittest.LoadFixtures()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2, RepoID: repo.ID}) @@ -124,58 +127,3 @@ func TestAPIMoveIssuePin(t *testing.T) { issueAPI4 := DecodeJSON(t, resp, &api.Issue{}) assert.Equal(t, 1, issueAPI4.PinOrder) } - -func TestAPIListPinnedIssues(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - // Pin the Issue - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Check if the Issue is in the List - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/pinned", repo.OwnerName, repo.Name)) - resp := MakeRequest(t, req, http.StatusOK) - issueList := DecodeJSON(t, resp, []api.Issue{}) - - assert.Len(t, issueList, 1) - assert.Equal(t, issue.ID, issueList[0].ID) -} - -func TestAPIListPinnedPullrequests(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/pinned", repo.OwnerName, repo.Name)) - resp := MakeRequest(t, req, http.StatusOK) - prList := DecodeJSON(t, resp, []api.PullRequest{}) - - assert.Empty(t, prList) -} - -func TestAPINewPinAllowed(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/new_pin_allowed", owner.Name, repo.Name)) - resp := MakeRequest(t, req, http.StatusOK) - - newPinsAllowed := DecodeJSON(t, resp, &api.NewIssuePinsAllowed{}) - - assert.True(t, newPinsAllowed.Issues) - assert.True(t, newPinsAllowed.PullRequests) -} diff --git a/tests/integration/api_issue_reaction_test.go b/tests/integration/api_issue_reaction_test.go index daa7bb4d17b..931881af063 100644 --- a/tests/integration/api_issue_reaction_test.go +++ b/tests/integration/api_issue_reaction_test.go @@ -31,143 +31,140 @@ func TestAPIIssuesReactions(t *testing.T) { session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner.Name, issue.Repo.Name, issue.Index) + t.Run("Issue", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner.Name, issue.Repo.Name, issue.Index) - // Try to add not allowed reaction - req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "wrong", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) + // Try to add not allowed reaction + req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "wrong", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) - // Delete not allowed reaction - req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ - Reaction: "zzz", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + // Delete not allowed reaction + req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + Reaction: "zzz", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) - // Add allowed reaction - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "rocket", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiNewReaction := DecodeJSON(t, resp, &api.Reaction{}) + // Add allowed reaction + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "rocket", + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + apiNewReaction := DecodeJSON(t, resp, &api.Reaction{}) - // Add existing reaction - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "rocket", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + // Add existing reaction + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "rocket", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) - // Blocked user can't react to comment - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "rocket", - }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteIssue)) - MakeRequest(t, req, http.StatusForbidden) + // Blocked user can't react to comment + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "rocket", + }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteIssue)) + MakeRequest(t, req, http.StatusForbidden) - // Get end result of reaction list of issue #1 - req = NewRequest(t, "GET", urlStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiReactions := DecodeJSON(t, resp, []*api.Reaction{}) - expectResponse := make(map[int]api.Reaction) - expectResponse[0] = api.Reaction{ - User: convert.ToUser(t.Context(), user2, user2), - Reaction: "eyes", - Created: time.Unix(1573248003, 0), - } - expectResponse[1] = *apiNewReaction - assert.Len(t, apiReactions, 2) - for i, r := range apiReactions { - assert.Equal(t, expectResponse[i].Reaction, r.Reaction) - assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) - assert.Equal(t, expectResponse[i].User.ID, r.User.ID) - } -} + // Get end result of reaction list of issue #1 + req = NewRequest(t, "GET", urlStr). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiReactions := DecodeJSON(t, resp, []*api.Reaction{}) + expectResponse := make(map[int]api.Reaction) + expectResponse[0] = api.Reaction{ + User: convert.ToUser(t.Context(), user2, user2), + Reaction: "eyes", + Created: time.Unix(1573248003, 0), + } + expectResponse[1] = *apiNewReaction + assert.Len(t, apiReactions, 2) + for i, r := range apiReactions { + assert.Equal(t, expectResponse[i].Reaction, r.Reaction) + assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) + assert.Equal(t, expectResponse[i].User.ID, r.User.ID) + } + }) -func TestAPICommentReactions(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("Comment", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + _ = comment.LoadIssue(t.Context()) + issue := comment.Issue + _ = issue.LoadRepo(t.Context()) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - _ = comment.LoadIssue(t.Context()) - issue := comment.Issue - _ = issue.LoadRepo(t.Context()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", owner.Name, issue.Repo.Name, comment.ID) - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + // Try to add not allowed reaction + req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "wrong", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", owner.Name, issue.Repo.Name, comment.ID) + // Delete none existing reaction + req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + Reaction: "eyes", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) - // Try to add not allowed reaction - req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "wrong", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) + t.Run("UnrelatedCommentID", func(t *testing.T) { + // Using the ID of a comment that does not belong to the repository must fail + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", repoOwner.Name, repo.Name, comment.ID) + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "+1", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + Reaction: "+1", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) - // Delete none existing reaction - req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ - Reaction: "eyes", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "GET", urlStr). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) - t.Run("UnrelatedCommentID", func(t *testing.T) { - // Using the ID of a comment that does not belong to the repository must fail - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", repoOwner.Name, repo.Name, comment.ID) + // Add allowed reaction req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ Reaction: "+1", }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + resp := MakeRequest(t, req, http.StatusCreated) + apiNewReaction := DecodeJSON(t, resp, &api.Reaction{}) + + // Add existing reaction + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ Reaction: "+1", }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + MakeRequest(t, req, http.StatusOK) + // Get end result of reaction list of issue #1 req = NewRequest(t, "GET", urlStr). AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + resp = MakeRequest(t, req, http.StatusOK) + apiReactions := DecodeJSON(t, resp, []*api.Reaction{}) + expectResponse := make(map[int]api.Reaction) + expectResponse[0] = api.Reaction{ + User: convert.ToUser(t.Context(), user2, user2), + Reaction: "laugh", + Created: time.Unix(1573248004, 0), + } + expectResponse[1] = api.Reaction{ + User: convert.ToUser(t.Context(), user1, user1), + Reaction: "laugh", + Created: time.Unix(1573248005, 0), + } + expectResponse[2] = *apiNewReaction + assert.Len(t, apiReactions, 3) + for i, r := range apiReactions { + assert.Equal(t, expectResponse[i].Reaction, r.Reaction) + assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) + assert.Equal(t, expectResponse[i].User.ID, r.User.ID) + } }) - - // Add allowed reaction - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "+1", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiNewReaction := DecodeJSON(t, resp, &api.Reaction{}) - - // Add existing reaction - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "+1", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - - // Get end result of reaction list of issue #1 - req = NewRequest(t, "GET", urlStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiReactions := DecodeJSON(t, resp, []*api.Reaction{}) - expectResponse := make(map[int]api.Reaction) - expectResponse[0] = api.Reaction{ - User: convert.ToUser(t.Context(), user2, user2), - Reaction: "laugh", - Created: time.Unix(1573248004, 0), - } - expectResponse[1] = api.Reaction{ - User: convert.ToUser(t.Context(), user1, user1), - Reaction: "laugh", - Created: time.Unix(1573248005, 0), - } - expectResponse[2] = *apiNewReaction - assert.Len(t, apiReactions, 3) - for i, r := range apiReactions { - assert.Equal(t, expectResponse[i].Reaction, r.Reaction) - assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) - assert.Equal(t, expectResponse[i].User.ID, r.User.ID) - } } diff --git a/tests/integration/api_issue_stopwatch_test.go b/tests/integration/api_issue_stopwatch_test.go index 225c64e5942..134bbb52de2 100644 --- a/tests/integration/api_issue_stopwatch_test.go +++ b/tests/integration/api_issue_stopwatch_test.go @@ -40,55 +40,36 @@ func TestAPIListStopWatches(t *testing.T) { assert.Equal(t, repo.OwnerName, apiWatches[0].RepoOwnerName) assert.Positive(t, apiWatches[0].Seconds) } -} - -func TestAPIStopStopWatches(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - _ = issue.LoadRepo(t.Context()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", owner.Name, issue.Repo.Name, issue.Index). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - MakeRequest(t, req, http.StatusConflict) -} - -func TestAPICancelStopWatches(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - _ = issue.LoadRepo(t.Context()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/stopwatch/delete", owner.Name, issue.Repo.Name, issue.Index). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - MakeRequest(t, req, http.StatusConflict) -} - -func TestAPIStartStopWatches(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) - _ = issue.LoadRepo(t.Context()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/start", owner.Name, issue.Repo.Name, issue.Index). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - MakeRequest(t, req, http.StatusConflict) + + for _, testCase := range []struct { + name string + issueID int64 + userID int64 + method string + urlFormat string + expectedCode int + }{ + {"stop", 2, 2, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", http.StatusCreated}, + {"cancel", 1, 1, "DELETE", "/api/v1/repos/%s/%s/issues/%d/stopwatch/delete", http.StatusNoContent}, + {"start", 3, 2, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/start", http.StatusCreated}, + } { + t.Run(testCase.name, func(t *testing.T) { + if testCase.name == "start" { + defer tests.PrepareTestEnv(t)() + } + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: testCase.issueID}) + _ = issue.LoadRepo(t.Context()) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.userID}) + + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + + req := NewRequestf(t, testCase.method, testCase.urlFormat, owner.Name, issue.Repo.Name, issue.Index). + AddTokenAuth(token) + MakeRequest(t, req, testCase.expectedCode) + MakeRequest(t, req, http.StatusConflict) + }) + } } diff --git a/tests/integration/api_issue_subscription_test.go b/tests/integration/api_issue_subscription_test.go index 34fe8c2a69e..75dea04a301 100644 --- a/tests/integration/api_issue_subscription_test.go +++ b/tests/integration/api_issue_subscription_test.go @@ -56,25 +56,19 @@ func TestAPIIssueSubscriptions(t *testing.T) { issue1Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue1.RepoID}) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s", issue1Repo.OwnerName, issue1Repo.Name, issue1.Index, owner.Name) - req := NewRequest(t, "DELETE", urlStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - testSubscription(issue1, false) - - req = NewRequest(t, "DELETE", urlStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - testSubscription(issue1, false) + for _, expectedCode := range []int{http.StatusCreated, http.StatusOK} { + req := NewRequest(t, "DELETE", urlStr). + AddTokenAuth(token) + MakeRequest(t, req, expectedCode) + testSubscription(issue1, false) + } issue5Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue5.RepoID}) urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s", issue5Repo.OwnerName, issue5Repo.Name, issue5.Index, owner.Name) - req = NewRequest(t, "PUT", urlStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - testSubscription(issue5, true) - - req = NewRequest(t, "PUT", urlStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - testSubscription(issue5, true) + for _, expectedCode := range []int{http.StatusCreated, http.StatusOK} { + req := NewRequest(t, "PUT", urlStr). + AddTokenAuth(token) + MakeRequest(t, req, expectedCode) + testSubscription(issue5, true) + } } diff --git a/tests/integration/api_issue_templates_test.go b/tests/integration/api_issue_templates_test.go deleted file mode 100644 index c5326cd2bfd..00000000000 --- a/tests/integration/api_issue_templates_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "net/url" - "testing" - - auth_model "gitea.dev/models/auth" - repo_model "gitea.dev/models/repo" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestAPIIssueTemplateList(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) - - // no issue template - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") - resp := MakeRequest(t, req, http.StatusOK) - issueTemplates := DecodeJSON(t, resp, []*api.IssueTemplate{}) - assert.Empty(t, issueTemplates) - - // one correct issue template and some incorrect issue templates - err := createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-ok.md", repo.DefaultBranch, `---- -name: foo -about: bar ----- -`) - assert.NoError(t, err) - - err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err1.yml", repo.DefaultBranch, `name: '`) - assert.NoError(t, err) - - err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err2.yml", repo.DefaultBranch, `other: `) - assert.NoError(t, err) - - req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") - resp = MakeRequest(t, req, http.StatusOK) - issueTemplates = DecodeJSON(t, resp, []*api.IssueTemplate{}) - assert.Len(t, issueTemplates, 1) - assert.Equal(t, "foo", issueTemplates[0].Name) - assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning")) - }) -} - -func TestAPIIssueTemplateRequiresCodeUnit(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) - issueTemplatesURL := "/api/v1/repos/" + repo.FullName() + "/issue_templates" - languagesURL := "/api/v1/repos/" + repo.FullName() + "/languages" - - req := NewRequest(t, "GET", issueTemplatesURL).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - req = NewRequest(t, "GET", languagesURL).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index 225d8104e78..c6ffef19d4e 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -27,6 +27,56 @@ import ( "github.com/stretchr/testify/assert" ) +func TestAPIIssueTemplateList(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + + // no issue template + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") + resp := MakeRequest(t, req, http.StatusOK) + issueTemplates := DecodeJSON(t, resp, []*api.IssueTemplate{}) + assert.Empty(t, issueTemplates) + + // one correct issue template and some incorrect issue templates + err := createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-ok.md", repo.DefaultBranch, `---- +name: foo +about: bar +---- +`) + assert.NoError(t, err) + + err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err1.yml", repo.DefaultBranch, `name: '`) + assert.NoError(t, err) + + err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err2.yml", repo.DefaultBranch, `other: `) + assert.NoError(t, err) + + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") + resp = MakeRequest(t, req, http.StatusOK) + issueTemplates = DecodeJSON(t, resp, []*api.IssueTemplate{}) + assert.Len(t, issueTemplates, 1) + assert.Equal(t, "foo", issueTemplates[0].Name) + assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning")) + }) +} + +func TestAPIIssueTemplateRequiresCodeUnit(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) + issueTemplatesURL := "/api/v1/repos/" + repo.FullName() + "/issue_templates" + languagesURL := "/api/v1/repos/" + repo.FullName() + "/languages" + + req := NewRequest(t, "GET", issueTemplatesURL).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", languagesURL).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) +} + func TestAPIIssue(t *testing.T) { defer tests.PrepareTestEnv(t)() t.Run("ListIssues", testAPIListIssues) diff --git a/tests/integration/api_issue_tracked_time_test.go b/tests/integration/api_issue_tracked_time_test.go index 0da5548a052..7640e25d51c 100644 --- a/tests/integration/api_issue_tracked_time_test.go +++ b/tests/integration/api_issue_tracked_time_test.go @@ -26,105 +26,103 @@ import ( func TestAPIGetTrackedTimes(t *testing.T) { defer tests.PrepareTestEnv(t)() - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - assert.NoError(t, issue2.LoadRepo(t.Context())) + t.Run("issue times", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + assert.NoError(t, issue2.LoadRepo(t.Context())) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times", user2.Name, issue2.Repo.Name, issue2.Index). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - apiTimes := DecodeJSON(t, resp, api.TrackedTimeList{}) - expect, err := issues_model.GetTrackedTimes(t.Context(), &issues_model.FindTrackedTimesOptions{IssueID: issue2.ID}) - assert.NoError(t, err) - assert.Len(t, apiTimes, 3) - - for i, time := range expect { - assert.Equal(t, time.ID, apiTimes[i].ID) - assert.Equal(t, issue2.Title, apiTimes[i].Issue.Title) - assert.Equal(t, issue2.ID, apiTimes[i].IssueID) - assert.Equal(t, time.Created.Unix(), apiTimes[i].Created.Unix()) - assert.Equal(t, time.Time, apiTimes[i].Time) - user, err := user_model.GetUserByID(t.Context(), time.UserID) - assert.NoError(t, err) - assert.Equal(t, user.Name, apiTimes[i].UserName) - } - - // test filter - since := "2000-01-01T00%3A00%3A02%2B00%3A00" // 946684802 - before := "2000-01-01T00%3A00%3A12%2B00%3A00" // 946684812 - - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times?since=%s&before=%s", user2.Name, issue2.Repo.Name, issue2.Index, since, before). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - filterAPITimes := DecodeJSON(t, resp, api.TrackedTimeList{}) - assert.Len(t, filterAPITimes, 2) - assert.Equal(t, int64(3), filterAPITimes[0].ID) - assert.Equal(t, int64(6), filterAPITimes[1].ID) -} - -// TestAPIGetTrackedTimesNonExistentUserFilter ensures filtering by a user that -// does not exist returns a clean 404 instead of panicking (nil pointer dereference). -func TestAPIGetTrackedTimesNonExistentUserFilter(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - assert.NoError(t, issue2.LoadRepo(t.Context())) - - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadRepository) - - for _, tc := range []struct { - name string - url string - }{ - {"repository level", fmt.Sprintf("/api/v1/repos/%s/%s/times?user=nonexistentuser", user2.Name, issue2.Repo.Name)}, - {"issue level", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/times?user=nonexistentuser", user2.Name, issue2.Repo.Name, issue2.Index)}, - } { - t.Run(tc.name, func(t *testing.T) { - req := NewRequest(t, "GET", tc.url).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusNotFound) - - assert.True(t, json.Valid(resp.Body.Bytes()), "response body must be a single JSON value, got: %s", resp.Body.Bytes()) - - var apiError api.APIError - DecodeJSON(t, resp, &apiError) - assert.Contains(t, apiError.Message, "user does not exist") - }) - } - - t.Run("existing user", func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/times?user=%s", user2.Name, issue2.Repo.Name, user2.Name).AddTokenAuth(token) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times", user2.Name, issue2.Repo.Name, issue2.Index). + AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, api.TrackedTimeList{}) - }) -} + apiTimes := DecodeJSON(t, resp, api.TrackedTimeList{}) + expect, err := issues_model.GetTrackedTimes(t.Context(), &issues_model.FindTrackedTimesOptions{IssueID: issue2.ID}) + assert.NoError(t, err) + assert.Len(t, apiTimes, 3) -func TestAPIUserTrackedTimesOmitsInaccessiblePrivateIssues(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - privateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, privateRepo.IsPrivate) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: privateRepo.ID}) - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), privateRepo, user, perm.AccessModeRead)) - _, err := issues_model.AddTime(t.Context(), user, issue, 60, time.Time{}) - assert.NoError(t, err) - assert.NoError(t, repo_service.DeleteCollaboration(t.Context(), privateRepo, user)) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) - req := NewRequest(t, "GET", "/api/v1/user/times").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - trackedTimes := DecodeJSON(t, resp, api.TrackedTimeList{}) - for _, trackedTime := range trackedTimes { - if assert.NotNil(t, trackedTime.Issue) { - assert.NotEqual(t, issue.ID, trackedTime.Issue.ID) + for i, time := range expect { + assert.Equal(t, time.ID, apiTimes[i].ID) + assert.Equal(t, issue2.Title, apiTimes[i].Issue.Title) + assert.Equal(t, issue2.ID, apiTimes[i].IssueID) + assert.Equal(t, time.Created.Unix(), apiTimes[i].Created.Unix()) + assert.Equal(t, time.Time, apiTimes[i].Time) + user, err := user_model.GetUserByID(t.Context(), time.UserID) + assert.NoError(t, err) + assert.Equal(t, user.Name, apiTimes[i].UserName) } - } + + // test filter + since := "2000-01-01T00%3A00%3A02%2B00%3A00" // 946684802 + before := "2000-01-01T00%3A00%3A12%2B00%3A00" // 946684812 + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times?since=%s&before=%s", user2.Name, issue2.Repo.Name, issue2.Index, since, before). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + filterAPITimes := DecodeJSON(t, resp, api.TrackedTimeList{}) + assert.Len(t, filterAPITimes, 2) + assert.Equal(t, int64(3), filterAPITimes[0].ID) + assert.Equal(t, int64(6), filterAPITimes[1].ID) + }) + + // TestAPIGetTrackedTimesNonExistentUserFilter ensures filtering by a user that + // does not exist returns a clean 404 instead of panicking (nil pointer dereference). + t.Run("nonexistent user filter", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + assert.NoError(t, issue2.LoadRepo(t.Context())) + + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadRepository) + + for _, tc := range []struct { + name string + url string + }{ + {"repository level", fmt.Sprintf("/api/v1/repos/%s/%s/times?user=nonexistentuser", user2.Name, issue2.Repo.Name)}, + {"issue level", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/times?user=nonexistentuser", user2.Name, issue2.Repo.Name, issue2.Index)}, + } { + t.Run(tc.name, func(t *testing.T) { + req := NewRequest(t, "GET", tc.url).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusNotFound) + + assert.True(t, json.Valid(resp.Body.Bytes()), "response body must be a single JSON value, got: %s", resp.Body.Bytes()) + + var apiError api.APIError + DecodeJSON(t, resp, &apiError) + assert.Contains(t, apiError.Message, "user does not exist") + }) + } + + t.Run("existing user", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/times?user=%s", user2.Name, issue2.Repo.Name, user2.Name).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, api.TrackedTimeList{}) + }) + }) + + t.Run("omit inaccessible private issues", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + privateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, privateRepo.IsPrivate) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: privateRepo.ID}) + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), privateRepo, user, perm.AccessModeRead)) + _, err := issues_model.AddTime(t.Context(), user, issue, 60, time.Time{}) + assert.NoError(t, err) + assert.NoError(t, repo_service.DeleteCollaboration(t.Context(), privateRepo, user)) + + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) + req := NewRequest(t, "GET", "/api/v1/user/times").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + trackedTimes := DecodeJSON(t, resp, api.TrackedTimeList{}) + for _, trackedTime := range trackedTimes { + if assert.NotNil(t, trackedTime.Issue) { + assert.NotEqual(t, issue.ID, trackedTime.Issue.ID) + } + } + }) } func TestAPIDeleteTrackedTime(t *testing.T) { diff --git a/tests/integration/api_keys_test.go b/tests/integration/api_keys_test.go index 3da6294238f..0224a4a0ca0 100644 --- a/tests/integration/api_keys_test.go +++ b/tests/integration/api_keys_test.go @@ -24,91 +24,78 @@ import ( func TestViewDeployKeysNoLogin(t *testing.T) { defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys") - MakeRequest(t, req, http.StatusUnauthorized) -} -func TestCreateDeployKeyNoLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/keys", api.CreateKeyOption{ - Title: "title", - Key: "key", - }) - MakeRequest(t, req, http.StatusUnauthorized) -} - -func TestGetDeployKeyNoLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys/1") - MakeRequest(t, req, http.StatusUnauthorized) -} - -func TestDeleteDeployKeyNoLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo1/keys/1") - MakeRequest(t, req, http.StatusUnauthorized) -} - -func TestCreateReadOnlyDeployKey(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) - rawKeyBody := api.CreateKeyOption{ - Title: "read-only", - Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - ReadOnly: true, - } - req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - - newDeployKey := DecodeJSON(t, resp, &api.DeployKey{}) - unittest.AssertExistsAndLoadBean(t, &deploykey_model.DeployKey{ - ID: newDeployKey.ID, - Name: rawKeyBody.Title, - Mode: perm.AccessModeRead, + t.Run("view", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys") + MakeRequest(t, req, http.StatusUnauthorized) }) - // Using the ID of a key that does not belong to the repository must fail - { - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/keys/%d", repoOwner.Name, repo.Name, newDeployKey.ID)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + t.Run("create", func(t *testing.T) { + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/keys", api.CreateKeyOption{ + Title: "title", + Key: "key", + }) + MakeRequest(t, req, http.StatusUnauthorized) + }) - session5 := loginUser(t, "user5") - token5 := getTokenForLoggedInUser(t, session5, auth_model.AccessTokenScopeWriteRepository) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user5/repo4/keys/%d", newDeployKey.ID)). - AddTokenAuth(token5) - MakeRequest(t, req, http.StatusNotFound) - } + t.Run("get", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys/1") + MakeRequest(t, req, http.StatusUnauthorized) + }) + + t.Run("delete", func(t *testing.T) { + req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo1/keys/1") + MakeRequest(t, req, http.StatusUnauthorized) + }) } -func TestCreateReadWriteDeployKey(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) +func TestCreateDeployKey(t *testing.T) { + for _, testCase := range []struct { + name string + readOnly bool + mode perm.AccessMode + }{ + {"read-only", true, perm.AccessModeRead}, + {"read-write", false, perm.AccessModeWrite}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) - rawKeyBody := api.CreateKeyOption{ - Title: "read-write", - Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) + rawKeyBody := api.CreateKeyOption{ + Title: testCase.name, + Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + ReadOnly: testCase.readOnly, + } + req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + newDeployKey := DecodeJSON(t, resp, &api.DeployKey{}) + unittest.AssertExistsAndLoadBean(t, &deploykey_model.DeployKey{ + ID: newDeployKey.ID, + Name: rawKeyBody.Title, + Mode: testCase.mode, + }) + + // Using the ID of a key that does not belong to the repository must fail + if testCase.readOnly { + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/keys/%d", repoOwner.Name, repo.Name, newDeployKey.ID)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + session5 := loginUser(t, "user5") + token5 := getTokenForLoggedInUser(t, session5, auth_model.AccessTokenScopeWriteRepository) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user5/repo4/keys/%d", newDeployKey.ID)). + AddTokenAuth(token5) + MakeRequest(t, req, http.StatusNotFound) + } + }) } - req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - - newDeployKey := DecodeJSON(t, resp, &api.DeployKey{}) - unittest.AssertExistsAndLoadBean(t, &deploykey_model.DeployKey{ - ID: newDeployKey.ID, - Name: rawKeyBody.Title, - Mode: perm.AccessModeWrite, - }) } func TestCreateUserKey(t *testing.T) { @@ -139,30 +126,25 @@ func TestCreateUserKey(t *testing.T) { }) // Search by fingerprint - req = NewRequest(t, "GET", "/api/v1/user/keys?fingerprint="+newPublicKey.Fingerprint). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) + for _, url := range []string{ + "/api/v1/user/keys?fingerprint=" + newPublicKey.Fingerprint, + fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", user.Name, newPublicKey.Fingerprint), + } { + req = NewRequest(t, "GET", url).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) - fingerprintPublicKeys := DecodeJSON(t, resp, []api.PublicKey{}) - assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) - assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) - assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", user.Name, newPublicKey.Fingerprint)). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - - fingerprintPublicKeys = DecodeJSON(t, resp, []api.PublicKey{}) - assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) - assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) - assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID) + fingerprintPublicKeys := DecodeJSON(t, resp, []api.PublicKey{}) + assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) + assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) + assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID) + } // Fail search by fingerprint req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/keys?fingerprint=%sA", newPublicKey.Fingerprint)). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) - fingerprintPublicKeys = DecodeJSON(t, resp, []api.PublicKey{}) + fingerprintPublicKeys := DecodeJSON(t, resp, []api.PublicKey{}) assert.Empty(t, fingerprintPublicKeys) // Fail searching for wrong users key @@ -178,24 +160,18 @@ func TestCreateUserKey(t *testing.T) { token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteUser) // Should find key even though not ours, but we shouldn't know whose it is - req = NewRequest(t, "GET", "/api/v1/user/keys?fingerprint="+newPublicKey.Fingerprint). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusOK) + for _, url := range []string{ + "/api/v1/user/keys?fingerprint=" + newPublicKey.Fingerprint, + fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", user.Name, newPublicKey.Fingerprint), + } { + req = NewRequest(t, "GET", url).AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusOK) - fingerprintPublicKeys = DecodeJSON(t, resp, []api.PublicKey{}) - assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) - assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) - assert.Nil(t, fingerprintPublicKeys[0].Owner) - - // Should find key even though not ours, but we shouldn't know whose it is - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", user.Name, newPublicKey.Fingerprint)). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusOK) - - fingerprintPublicKeys = DecodeJSON(t, resp, []api.PublicKey{}) - assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) - assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) - assert.Nil(t, fingerprintPublicKeys[0].Owner) + fingerprintPublicKeys := DecodeJSON(t, resp, []api.PublicKey{}) + assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) + assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) + assert.Nil(t, fingerprintPublicKeys[0].Owner) + } // Fail when searching for key if it is not ours req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", "user2", newPublicKey.Fingerprint)). diff --git a/tests/integration/api_label_templates_test.go b/tests/integration/api_label_templates_test.go index 66d4b864260..9427f219636 100644 --- a/tests/integration/api_label_templates_test.go +++ b/tests/integration/api_label_templates_test.go @@ -9,7 +9,9 @@ import ( "strings" "testing" + "gitea.dev/modules/options" repo_module "gitea.dev/modules/repository" + "gitea.dev/modules/setting" api "gitea.dev/modules/structs" "gitea.dev/tests" @@ -19,40 +21,75 @@ import ( func TestAPIListLabelTemplates(t *testing.T) { defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/label/templates") - resp := MakeRequest(t, req, http.StatusOK) + t.Run("list labels", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/label/templates") + resp := MakeRequest(t, req, http.StatusOK) - templateList := DecodeJSON(t, resp, []string{}) + templateList := DecodeJSON(t, resp, []string{}) - for i := range repo_module.LabelTemplateFiles { - assert.Equal(t, repo_module.LabelTemplateFiles[i].DisplayName, templateList[i]) - } -} - -func TestAPIGetLabelTemplateInfo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // If Gitea has for some reason no Label templates, we need to skip this test - if len(repo_module.LabelTemplateFiles) == 0 { - return - } - - // Use the first template for the test - templateName := repo_module.LabelTemplateFiles[0].DisplayName - - urlStr := "/api/v1/label/templates/" + url.PathEscape(templateName) - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - templateInfo := DecodeJSON(t, resp, []api.LabelTemplate{}) - - labels, err := repo_module.LoadTemplateLabelsByDisplayName(templateName) - assert.NoError(t, err) - - for i := range labels { - assert.Equal(t, strings.TrimLeft(labels[i].Color, "#"), templateInfo[i].Color) - assert.Equal(t, labels[i].Description, templateInfo[i].Description) - assert.Equal(t, labels[i].Exclusive, templateInfo[i].Exclusive) - assert.Equal(t, labels[i].Name, templateInfo[i].Name) - } + for i := range repo_module.LabelTemplateFiles { + assert.Equal(t, repo_module.LabelTemplateFiles[i].DisplayName, templateList[i]) + } + }) + + t.Run("label info", func(t *testing.T) { + // If Gitea has for some reason no Label templates, we need to skip this test + if len(repo_module.LabelTemplateFiles) == 0 { + return + } + + // Use the first template for the test + templateName := repo_module.LabelTemplateFiles[0].DisplayName + + urlStr := "/api/v1/label/templates/" + url.PathEscape(templateName) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + templateInfo := DecodeJSON(t, resp, []api.LabelTemplate{}) + + labels, err := repo_module.LoadTemplateLabelsByDisplayName(templateName) + assert.NoError(t, err) + + for i := range labels { + assert.Equal(t, strings.TrimLeft(labels[i].Color, "#"), templateInfo[i].Color) + assert.Equal(t, labels[i].Description, templateInfo[i].Description) + assert.Equal(t, labels[i].Exclusive, templateInfo[i].Exclusive) + assert.Equal(t, labels[i].Name, templateInfo[i].Name) + } + }) + + t.Run("list licenses", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/licenses") + resp := MakeRequest(t, req, http.StatusOK) + + licenseList := DecodeJSON(t, resp, []api.LicensesTemplateListEntry{}) + assert.Contains(t, licenseList, api.LicensesTemplateListEntry{ + Key: "MIT", + Name: "MIT", + URL: setting.AppURL + "api/v1/licenses/MIT", + }) + }) + + t.Run("license info", func(t *testing.T) { + // If Gitea has for some reason no License templates, we need to skip this test + if len(repo_module.Licenses) == 0 { + return + } + + // Use the first template for the test + licenseName := repo_module.Licenses[0] + + urlStr := "/api/v1/licenses/" + url.PathEscape(licenseName) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + licenseInfo := DecodeJSON(t, resp, &api.LicenseTemplateInfo{}) + + // We get the text of the template here + text, _ := options.License(licenseName) + + assert.Equal(t, licenseInfo.Key, licenseName) + assert.Equal(t, licenseInfo.Name, licenseName) + assert.Equal(t, licenseInfo.Body, string(text)) + }) } diff --git a/tests/integration/api_license_templates_test.go b/tests/integration/api_license_templates_test.go deleted file mode 100644 index 2f3d3b08305..00000000000 --- a/tests/integration/api_license_templates_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "net/url" - "testing" - - "gitea.dev/modules/options" - repo_module "gitea.dev/modules/repository" - "gitea.dev/modules/setting" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestAPIListLicenseTemplates(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/api/v1/licenses") - resp := MakeRequest(t, req, http.StatusOK) - - licenseList := DecodeJSON(t, resp, []api.LicensesTemplateListEntry{}) - assert.Contains(t, licenseList, api.LicensesTemplateListEntry{ - Key: "MIT", - Name: "MIT", - URL: setting.AppURL + "api/v1/licenses/MIT", - }) -} - -func TestAPIGetLicenseTemplateInfo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // If Gitea has for some reason no License templates, we need to skip this test - if len(repo_module.Licenses) == 0 { - return - } - - // Use the first template for the test - licenseName := repo_module.Licenses[0] - - urlStr := "/api/v1/licenses/" + url.PathEscape(licenseName) - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - licenseInfo := DecodeJSON(t, resp, &api.LicenseTemplateInfo{}) - - // We get the text of the template here - text, _ := options.License(licenseName) - - assert.Equal(t, licenseInfo.Key, licenseName) - assert.Equal(t, licenseInfo.Name, licenseName) - assert.Equal(t, licenseInfo.Body, string(text)) -} diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go index 2a856d4da42..6c87046a63a 100644 --- a/tests/integration/api_notification_test.go +++ b/tests/integration/api_notification_test.go @@ -22,6 +22,24 @@ import ( func TestAPINotification(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("public only", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) + + token := getUserToken(t, user2.Name, auth_model.AccessTokenScopeReadNotification, auth_model.AccessTokenScopePublicOnly) + req := NewRequest(t, "GET", "/api/v1/notifications"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", "/api/v1/notifications/new"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d", thread5.ID)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) @@ -209,23 +227,3 @@ func TestAPINotificationPUT(t *testing.T) { assert.True(t, apiNL[0].Unread) assert.False(t, apiNL[0].Pinned) } - -func TestAPINotificationPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) - - token := getUserToken(t, user2.Name, auth_model.AccessTokenScopeReadNotification, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/notifications"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - req = NewRequest(t, "GET", "/api/v1/notifications/new"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d", thread5.ID)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} diff --git a/tests/integration/api_org_avatar_test.go b/tests/integration/api_org_avatar_test.go deleted file mode 100644 index 7727331dda9..00000000000 --- a/tests/integration/api_org_avatar_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "encoding/base64" - "net/http" - "os" - "path/filepath" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/modules/setting" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestAPIUpdateOrgAvatar(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user1") - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - - // Test what happens if you use a valid image - avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) - assert.NoError(t, err) - if err != nil { - assert.FailNow(t, "Unable to open avatar.png") - } - - opts := api.UpdateUserAvatarOption{ - Image: base64.StdEncoding.EncodeToString(avatar), - } - - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Test what happens if you don't have a valid Base64 string - opts = api.UpdateUserAvatarOption{ - Image: "Invalid", - } - - req = NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) - - // Test what happens if you use a file that is not an image - opts = api.UpdateUserAvatarOption{ - Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")), - } - - req = NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusInternalServerError) -} - -func TestAPIDeleteOrgAvatar(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user1") - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - - req := NewRequest(t, "DELETE", "/api/v1/orgs/org3/avatar"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) -} diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 5e62f6e0410..c4ebbf309a1 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -4,8 +4,11 @@ package integration import ( + "encoding/base64" "fmt" "net/http" + "os" + "path/filepath" "strings" "testing" "time" @@ -27,8 +30,127 @@ import ( "github.com/stretchr/testify/require" ) +func TestAPIUpdateOrgAvatar(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + + // Test what happens if you use a valid image + avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) + assert.NoError(t, err) + if err != nil { + assert.FailNow(t, "Unable to open avatar.png") + } + + for _, testCase := range []struct { + image string + status int + }{ + {base64.StdEncoding.EncodeToString(avatar), http.StatusNoContent}, + // Test what happens if you don't have a valid Base64 string + {"Invalid", http.StatusBadRequest}, + // Test what happens if you use a file that is not an image + {base64.StdEncoding.EncodeToString([]byte("This is not an image")), http.StatusInternalServerError}, + } { + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &api.UpdateUserAvatarOption{ + Image: testCase.image, + }).AddTokenAuth(token) + MakeRequest(t, req, testCase.status) + } +} + +func TestAPIDeleteOrgAvatar(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + + req := NewRequest(t, "DELETE", "/api/v1/orgs/org3/avatar"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) +} + func TestAPIOrg(t *testing.T) { defer tests.PrepareTestEnv(t)() + + // TestAPIOrgLabelsVisibility ensures the organization label read endpoints honor + // the organization visibility: labels of a private org must not be disclosed to + // users who cannot see the org (GHSA: unauthorized access to private org labels). + t.Run("LabelsVisibility", func(t *testing.T) { + // privated_org (id 23) is a private organization; user5 is its only member. + privateOrg := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 23}) + label := &issues_model.Label{OrgID: privateOrg.ID, Name: "internal-label", Color: "#aabbcc", Description: "private organization label"} + require.NoError(t, issues_model.NewLabel(t.Context(), label)) + + listURL := fmt.Sprintf("/api/v1/orgs/%s/labels", privateOrg.Name) + getURL := fmt.Sprintf("/api/v1/orgs/%s/labels/%d", privateOrg.Name, label.ID) + + t.Run("NonMemberDenied", func(t *testing.T) { + // user2 is not a member of the private org and must not see its labels. + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) + MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusNotFound) + }) + + t.Run("AnonymousDenied", func(t *testing.T) { + MakeRequest(t, NewRequest(t, "GET", listURL), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "GET", getURL), http.StatusNotFound) + }) + + t.Run("MemberAllowed", func(t *testing.T) { + token := getUserToken(t, "user5", auth_model.AccessTokenScopeReadOrganization) + resp := MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK) + labels := DecodeJSON(t, resp, &[]*api.Label{}) + assert.Len(t, *labels, 1) + MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK) + }) + + t.Run("SiteAdminAllowed", func(t *testing.T) { + token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization) + MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK) + MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK) + }) + + t.Run("PublicOrgStillReadable", func(t *testing.T) { + // org3 (id 3) is a public org with labels; non-members may read them. + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) + MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/labels").AddTokenAuth(token), http.StatusOK) + }) + }) + + t.Run("PrivateMembersNotLeaked", func(t *testing.T) { + // privated_org (org 23) has private visibility and a single member, user5 + const orgName = "privated_org" + const memberName = "user5" + + // member publicizes their own membership inside the private org + memberSession := loginUser(t, memberName) + memberToken := getTokenForLoggedInUser(t, memberSession, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequest(t, "PUT", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusNoContent) + + // an outsider must not be able to learn about the membership of a private org + outsiderSession := loginUser(t, "user2") + outsiderToken := getTokenForLoggedInUser(t, outsiderSession, auth_model.AccessTokenScopeReadOrganization) + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members").AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + // the full member list of a private org must not be enumerable by an outsider either + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/members").AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + + // the member can still see the public membership of their own org + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusNoContent) + // and the member can still list the org's members + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/members").AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusOK) + }) + t.Run("General", testAPIOrgGeneral) t.Run("CreateAndRename", testAPIOrgCreateRename) t.Run("DeleteOrgRepos", testAPIDeleteOrgRepos) @@ -275,38 +397,6 @@ func testAPIOrgGeneral(t *testing.T) { }) } -func TestAPIOrgPrivateMembersNotLeaked(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // privated_org (org 23) has private visibility and a single member, user5 - const orgName = "privated_org" - const memberName = "user5" - - // member publicizes their own membership inside the private org - memberSession := loginUser(t, memberName) - memberToken := getTokenForLoggedInUser(t, memberSession, auth_model.AccessTokenScopeWriteOrganization) - req := NewRequest(t, "PUT", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) - MakeRequest(t, req, http.StatusNoContent) - - // an outsider must not be able to learn about the membership of a private org - outsiderSession := loginUser(t, "user2") - outsiderToken := getTokenForLoggedInUser(t, outsiderSession, auth_model.AccessTokenScopeReadOrganization) - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(outsiderToken) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members").AddTokenAuth(outsiderToken) - MakeRequest(t, req, http.StatusNotFound) - // the full member list of a private org must not be enumerable by an outsider either - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/members").AddTokenAuth(outsiderToken) - MakeRequest(t, req, http.StatusNotFound) - - // the member can still see the public membership of their own org - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) - MakeRequest(t, req, http.StatusNoContent) - // and the member can still list the org's members - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/members").AddTokenAuth(memberToken) - MakeRequest(t, req, http.StatusOK) -} - func testAPIDeleteOrgRepos(t *testing.T) { org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) orgRepos, err := repo_model.GetOrgRepositories(t.Context(), org3.ID) @@ -336,50 +426,3 @@ func testAPIDeleteOrgRepos(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) // The org contains no repositories, so the API should return StatusNoContent }) } - -// TestAPIOrgLabelsVisibility ensures the organization label read endpoints honor -// the organization visibility: labels of a private org must not be disclosed to -// users who cannot see the org (GHSA: unauthorized access to private org labels). -func TestAPIOrgLabelsVisibility(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // privated_org (id 23) is a private organization; user5 is its only member. - privateOrg := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 23}) - label := &issues_model.Label{OrgID: privateOrg.ID, Name: "internal-label", Color: "#aabbcc", Description: "private organization label"} - require.NoError(t, issues_model.NewLabel(t.Context(), label)) - - listURL := fmt.Sprintf("/api/v1/orgs/%s/labels", privateOrg.Name) - getURL := fmt.Sprintf("/api/v1/orgs/%s/labels/%d", privateOrg.Name, label.ID) - - t.Run("NonMemberDenied", func(t *testing.T) { - // user2 is not a member of the private org and must not see its labels. - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) - MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusNotFound) - MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusNotFound) - }) - - t.Run("AnonymousDenied", func(t *testing.T) { - MakeRequest(t, NewRequest(t, "GET", listURL), http.StatusNotFound) - MakeRequest(t, NewRequest(t, "GET", getURL), http.StatusNotFound) - }) - - t.Run("MemberAllowed", func(t *testing.T) { - token := getUserToken(t, "user5", auth_model.AccessTokenScopeReadOrganization) - resp := MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK) - labels := DecodeJSON(t, resp, &[]*api.Label{}) - assert.Len(t, *labels, 1) - MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK) - }) - - t.Run("SiteAdminAllowed", func(t *testing.T) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization) - MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK) - MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK) - }) - - t.Run("PublicOrgStillReadable", func(t *testing.T) { - // org3 (id 3) is a public org with labels; non-members may read them. - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) - MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/labels").AddTokenAuth(token), http.StatusOK) - }) -} diff --git a/tests/integration/api_packages_arch_test.go b/tests/integration/api_packages_arch_test.go index ece8b27f4c6..539712f93c2 100644 --- a/tests/integration/api_packages_arch_test.go +++ b/tests/integration/api_packages_arch_test.go @@ -85,6 +85,16 @@ license = MIT`) rootURL := fmt.Sprintf("/api/packages/%s/arch", user.Name) + uploadPackage := func(t *testing.T, url string, content []byte, expectedStatus int) { + MakeRequest(t, NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).AddBasicAuth(user.Name), expectedStatus) + } + + readIndex := func(t *testing.T, url string) map[string]string { + content, err := test.ReadAllTarGzContent(MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK).Body) + assert.NoError(t, err) + return content + } + t.Run("RepositoryKey", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -110,13 +120,9 @@ license = MIT`) req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusBadRequest) + uploadPackage(t, uploadURL, []byte{}, http.StatusBadRequest) - req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + uploadPackage(t, uploadURL, contentAarch64, http.StatusCreated) pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeArch) assert.NoError(t, err) @@ -161,24 +167,16 @@ license = MIT`) return seen }) - req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) + uploadPackage(t, uploadURL, contentAarch64, http.StatusConflict) // Add same package with different compression leads to conflict - req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64Gz)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) + uploadPackage(t, uploadURL, contentAarch64Gz, http.StatusConflict) }) t.Run("Index", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) - resp := MakeRequest(t, req, http.StatusOK) - - content, err := test.ReadAllTarGzContent(resp.Body) - assert.NoError(t, err) + content := readIndex(t, fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)] assert.True(t, has) @@ -222,15 +220,9 @@ license = MIT`) t.Run("Any", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s", rootURL, repository), bytes.NewReader(contentAny)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + uploadPackage(t, fmt.Sprintf("%s/%s", rootURL, repository), contentAny, http.StatusCreated) - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) - resp := MakeRequest(t, req, http.StatusOK) - - content, err := test.ReadAllTarGzContent(resp.Body) - assert.NoError(t, err) + content := readIndex(t, fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)] assert.True(t, has) @@ -248,7 +240,7 @@ license = MIT`) MakeRequest(t, req, http.StatusOK) } - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/any", rootURL, repository, packageName+"_"+arch_module.AnyArch, packageVersion)). + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/any", rootURL, repository, packageName+"_"+arch_module.AnyArch, packageVersion)). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) }) @@ -274,18 +266,10 @@ license = MIT`) defer tests.PrintCurrentTest(t)() pkgVer1 := createPackage("gz", "gitea-test", "1.0.0", "aarch64") pkgVer2 := createPackage("gz", "gitea-test", "1.0.1", "aarch64") - req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer1)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer2)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + uploadPackage(t, rootURL, pkgVer1, http.StatusCreated) + uploadPackage(t, rootURL, pkgVer2, http.StatusCreated) - req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) - resp := MakeRequest(t, req, http.StatusOK) - - content, err := test.ReadAllTarGzContent(resp.Body) - assert.NoError(t, err) + content := readIndex(t, fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) assert.Len(t, content, 2) _, has := content["gitea-test-1.0.0/desc"] @@ -293,14 +277,11 @@ license = MIT`) _, has = content["gitea-test-1.0.1/desc"] assert.True(t, has) - req = NewRequest(t, "DELETE", rootURL+"/gitea-test/1.0.1/aarch64"). + req := NewRequest(t, "DELETE", rootURL+"/gitea-test/1.0.1/aarch64"). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) - resp = MakeRequest(t, req, http.StatusOK) - content, err = test.ReadAllTarGzContent(resp.Body) - assert.NoError(t, err) + content = readIndex(t, fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) assert.Len(t, content, 2) _, has = content["gitea-test-1.0.0/desc"] assert.True(t, has) diff --git a/tests/integration/api_packages_chef_test.go b/tests/integration/api_packages_chef_test.go index f179baac3de..8ba1467f289 100644 --- a/tests/integration/api_packages_chef_test.go +++ b/tests/integration/api_packages_chef_test.go @@ -128,29 +128,14 @@ nwIDAQAB req := NewRequest(t, "POST", "/dummy"). SetHeader("X-Ops-Userid", user.Name). SetHeader("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339)) - u, err := auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) - - req.SetHeader("X-Ops-Sign", "version=none") - u, err = auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) - - req.SetHeader("X-Ops-Sign", "version=1.4") - u, err = auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) - - req.SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha2") - u, err = auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) - - req.SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha256") - u, err = auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) + for _, signingVersion := range []string{"", "version=none", "version=1.4", "version=1.0;algorithm=sha2", "version=1.0;algorithm=sha256"} { + if signingVersion != "" { + req.SetHeader("X-Ops-Sign", signingVersion) + } + u, err := auth.Verify(req.Request, nil, nil, nil) + assert.Nil(t, u) + assert.Error(t, err) + } }) t.Run("SignedHeaders", func(t *testing.T) { @@ -362,105 +347,67 @@ nwIDAQAB assert.Equal(t, fmt.Sprintf("%s%s/cookbooks/%s/versions/%s/download", setting.AppURL, root[1:], packageName, packageVersion), info.DownloadURL) }) - t.Run("Search", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + type listCase struct { + Path string + ExpectedTotal int + ExpectedResults int + } - cases := []struct { - Query string - Start int - Items int - ExpectedTotal int - ExpectedResults int - }{ - {"", 0, 0, 1, 1}, - {"", 0, 10, 1, 1}, - {"gitea", 0, 10, 0, 0}, - {"test", 0, 10, 1, 1}, - {"test", 1, 10, 1, 0}, - } + for _, list := range []struct { + name string + cases []listCase + }{ + {"Search", []listCase{ + {"/search?q=&start=0&items=0", 1, 1}, + {"/search?q=&start=0&items=10", 1, 1}, + {"/search?q=gitea&start=0&items=10", 0, 0}, + {"/search?q=test&start=0&items=10", 1, 1}, + {"/search?q=test&start=1&items=10", 1, 0}, + }}, + {"EnumeratePackages", []listCase{ + {"/cookbooks?start=0&items=0&sort=", 1, 1}, + {"/cookbooks?start=0&items=10&sort=", 1, 1}, + {"/cookbooks?start=0&items=10&sort=RECENTLY_ADDED", 1, 1}, + {"/cookbooks?start=0&items=10&sort=RECENTLY_UPDATED", 1, 1}, + {"/cookbooks?start=1&items=10&sort=", 1, 0}, + }}, + } { + t.Run(list.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - type Item struct { - CookbookName string `json:"cookbook_name"` - CookbookMaintainer string `json:"cookbook_maintainer"` - CookbookDescription string `json:"cookbook_description"` - Cookbook string `json:"cookbook"` - } - - type Result struct { - Start int `json:"start"` - Total int `json:"total"` - Items []*Item `json:"items"` - } - - for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/search?q=%s&start=%d&items=%d", root, c.Query, c.Start, c.Items)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - result := DecodeJSON(t, resp, &Result{}) - - assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i) - assert.Len(t, result.Items, c.ExpectedResults, "case %d: unexpected result count", i) - - if len(result.Items) == 1 { - item := result.Items[0] - assert.Equal(t, packageName, item.CookbookName) - assert.Equal(t, packageAuthor, item.CookbookMaintainer) - assert.Equal(t, packageDescription, item.CookbookDescription) - assert.Equal(t, fmt.Sprintf("%s%s/cookbooks/%s", setting.AppURL, root[1:], packageName), item.Cookbook) + type Item struct { + CookbookName string `json:"cookbook_name"` + CookbookMaintainer string `json:"cookbook_maintainer"` + CookbookDescription string `json:"cookbook_description"` + Cookbook string `json:"cookbook"` } - } - }) - t.Run("EnumeratePackages", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - cases := []struct { - Sort string - Start int - Items int - ExpectedTotal int - ExpectedResults int - }{ - {"", 0, 0, 1, 1}, - {"", 0, 10, 1, 1}, - {"RECENTLY_ADDED", 0, 10, 1, 1}, - {"RECENTLY_UPDATED", 0, 10, 1, 1}, - {"", 1, 10, 1, 0}, - } - - type Item struct { - CookbookName string `json:"cookbook_name"` - CookbookMaintainer string `json:"cookbook_maintainer"` - CookbookDescription string `json:"cookbook_description"` - Cookbook string `json:"cookbook"` - } - - type Result struct { - Start int `json:"start"` - Total int `json:"total"` - Items []*Item `json:"items"` - } - - for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/cookbooks?start=%d&items=%d&sort=%s", root, c.Start, c.Items, c.Sort)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - result := DecodeJSON(t, resp, &Result{}) - - assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i) - assert.Len(t, result.Items, c.ExpectedResults, "case %d: unexpected result count", i) - - if len(result.Items) == 1 { - item := result.Items[0] - assert.Equal(t, packageName, item.CookbookName) - assert.Equal(t, packageAuthor, item.CookbookMaintainer) - assert.Equal(t, packageDescription, item.CookbookDescription) - assert.Equal(t, fmt.Sprintf("%s%s/cookbooks/%s", setting.AppURL, root[1:], packageName), item.Cookbook) + type Result struct { + Start int `json:"start"` + Total int `json:"total"` + Items []*Item `json:"items"` } - } - }) + + for i, c := range list.cases { + req := NewRequest(t, "GET", root+c.Path). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + result := DecodeJSON(t, resp, &Result{}) + + assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i) + assert.Len(t, result.Items, c.ExpectedResults, "case %d: unexpected result count", i) + + if len(result.Items) == 1 { + item := result.Items[0] + assert.Equal(t, packageName, item.CookbookName) + assert.Equal(t, packageAuthor, item.CookbookMaintainer) + assert.Equal(t, packageDescription, item.CookbookDescription) + assert.Equal(t, fmt.Sprintf("%s%s/cookbooks/%s", setting.AppURL, root[1:], packageName), item.Cookbook) + } + } + }) + } t.Run("PackageMetadata", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index d464c5df8b5..5b02771be89 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -196,25 +196,30 @@ func TestPackageComposer(t *testing.T) { t.Run("PackageMetadata", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) + assertPackageMetadata := func() []*composer.PackageVersionMetadata { + req := NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) - result := DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) + result := DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) - assert.Contains(t, result.Packages, packageName) - pkgs := result.Packages[packageName] - assert.Len(t, pkgs, 1) - assert.Equal(t, packageName, pkgs[0].Name) - assert.Equal(t, packageVersion, pkgs[0].Version) - assert.Equal(t, packageType, pkgs[0].Type) - assert.Equal(t, packageDescription, pkgs[0].Description) - assert.Len(t, pkgs[0].Authors, 1) - assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) - assert.Equal(t, "zip", pkgs[0].Dist.Type) - assert.Equal(t, hex.EncodeToString(contentChecksum[:]), pkgs[0].Dist.Checksum) - assert.Len(t, pkgs[0].Bin, 1) - assert.Equal(t, packageBin, pkgs[0].Bin[0]) + assert.Contains(t, result.Packages, packageName) + pkgs := result.Packages[packageName] + assert.Len(t, pkgs, 1) + assert.Equal(t, packageName, pkgs[0].Name) + assert.Equal(t, packageVersion, pkgs[0].Version) + assert.Equal(t, packageType, pkgs[0].Type) + assert.Equal(t, packageDescription, pkgs[0].Description) + assert.Len(t, pkgs[0].Authors, 1) + assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) + assert.Equal(t, "zip", pkgs[0].Dist.Type) + assert.Equal(t, hex.EncodeToString(contentChecksum[:]), pkgs[0].Dist.Checksum) + assert.Len(t, pkgs[0].Bin, 1) + assert.Equal(t, packageBin, pkgs[0].Bin[0]) + return pkgs + } + + assertPackageMetadata() // Test package linked to repository repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) @@ -226,25 +231,7 @@ func TestPackageComposer(t *testing.T) { err = packages.SetRepositoryLink(t.Context(), userPkgs[0].ID, repo1.ID) assert.NoError(t, err) - req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) - - assert.Contains(t, result.Packages, packageName) - pkgs = result.Packages[packageName] - assert.Len(t, pkgs, 1) - assert.Equal(t, packageName, pkgs[0].Name) - assert.Equal(t, packageVersion, pkgs[0].Version) - assert.Equal(t, packageType, pkgs[0].Type) - assert.Equal(t, packageDescription, pkgs[0].Description) - assert.Len(t, pkgs[0].Authors, 1) - assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) - assert.Equal(t, "zip", pkgs[0].Dist.Type) - assert.Equal(t, hex.EncodeToString(contentChecksum[:]), pkgs[0].Dist.Checksum) - assert.Len(t, pkgs[0].Bin, 1) - assert.Equal(t, packageBin, pkgs[0].Bin[0]) + pkgs := assertPackageMetadata() assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL) assert.Equal(t, "git", pkgs[0].Source.Type) assert.Equal(t, packageVersion, pkgs[0].Source.Reference) @@ -254,11 +241,11 @@ func TestPackageComposer(t *testing.T) { err = packages.SetRepositoryLink(t.Context(), userPkgs[0].ID, repo2.ID) assert.NoError(t, err) - req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + req := NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) + resp := MakeRequest(t, req, http.StatusOK) - result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) + result := DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) pkgs = result.Packages[packageName] assert.Len(t, pkgs, 1) assert.Equal(t, repo2.HTMLURL(), pkgs[0].Source.URL) diff --git a/tests/integration/api_packages_conan_test.go b/tests/integration/api_packages_conan_test.go index 90fd3b85bfa..c72db1e70e4 100644 --- a/tests/integration/api_packages_conan_test.go +++ b/tests/integration/api_packages_conan_test.go @@ -278,29 +278,23 @@ func TestPackageConan(t *testing.T) { MakeRequest(t, req, expectedStatusCode) } - t.Run("No Package permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, permission := range []struct { + name string + scope auth_model.AccessTokenScope + expectedAuthStatusCode int + expectedStatusCode int + }{ + {"No Package permission", auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusForbidden}, + {"Package Read permission", auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized}, + {"Package Write permission", auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusOK}, + {"All permission", auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusOK}, + } { + t.Run(permission.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - testCase(t, auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusForbidden) - }) - - t.Run("Package Read permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized) - }) - - t.Run("Package Write permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusOK) - }) - - t.Run("All permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusOK) - }) + testCase(t, permission.scope, permission.expectedAuthStatusCode, permission.expectedStatusCode) + }) + } }) t.Run("CheckCredentials", func(t *testing.T) { @@ -366,55 +360,36 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, channel1) - req := NewRequest(t, "GET", recipeURL) - resp := MakeRequest(t, req, http.StatusOK) + for _, download := range []struct { + url string + fileName string + hash string + content string + }{ + {recipeURL, conanfileName, "7abc52241c22090782c54731371847a8", buildConanfileContent(name, version1)}, + {fmt.Sprintf("%s/packages/%s", recipeURL, conanPackageReference), conaninfoName, "7628bfcc5b17f1470c468621a78df394", contentConaninfo}, + } { + req := NewRequest(t, "GET", download.url) + resp := MakeRequest(t, req, http.StatusOK) - fileHashes := DecodeJSON(t, resp, map[string]string{}) - assert.Len(t, fileHashes, 1) - assert.Contains(t, fileHashes, conanfileName) - assert.Equal(t, "7abc52241c22090782c54731371847a8", fileHashes[conanfileName]) + fileHashes := DecodeJSON(t, resp, map[string]string{}) + assert.Len(t, fileHashes, 1) + assert.Contains(t, fileHashes, download.fileName) + assert.Equal(t, download.hash, fileHashes[download.fileName]) - req = NewRequest(t, "GET", recipeURL+"/digest") - resp = MakeRequest(t, req, http.StatusOK) + var downloadURLs map[string]string + for _, endpoint := range []string{"/digest", "/download_urls"} { + req = NewRequest(t, "GET", download.url+endpoint) + resp = MakeRequest(t, req, http.StatusOK) - downloadURLs := DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, downloadURLs, conanfileName) + downloadURLs = DecodeJSON(t, resp, map[string]string{}) + assert.Contains(t, downloadURLs, download.fileName) + } - req = NewRequest(t, "GET", recipeURL+"/download_urls") - resp = MakeRequest(t, req, http.StatusOK) - - downloadURLs = DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, downloadURLs, conanfileName) - - req = NewRequest(t, "GET", downloadURLs[conanfileName]) - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, buildConanfileContent(name, version1), resp.Body.String()) - - packageURL := fmt.Sprintf("%s/packages/%s", recipeURL, conanPackageReference) - - req = NewRequest(t, "GET", packageURL) - resp = MakeRequest(t, req, http.StatusOK) - - fileHashes = DecodeJSON(t, resp, map[string]string{}) - assert.Len(t, fileHashes, 1) - assert.Contains(t, fileHashes, conaninfoName) - assert.Equal(t, "7628bfcc5b17f1470c468621a78df394", fileHashes[conaninfoName]) - - req = NewRequest(t, "GET", packageURL+"/digest") - resp = MakeRequest(t, req, http.StatusOK) - - downloadURLs = DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, downloadURLs, conaninfoName) - - req = NewRequest(t, "GET", packageURL+"/download_urls") - resp = MakeRequest(t, req, http.StatusOK) - - downloadURLs = DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, downloadURLs, conaninfoName) - - req = NewRequest(t, "GET", downloadURLs[conaninfoName]) - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, contentConaninfo, resp.Body.String()) + req = NewRequest(t, "GET", downloadURLs[download.fileName]) + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, download.content, resp.Body.String()) + } }) t.Run("Search", func(t *testing.T) { @@ -605,29 +580,23 @@ func TestPackageConan(t *testing.T) { MakeRequest(t, req, expectedStatusCode) } - t.Run("No Package permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, permission := range []struct { + name string + scope auth_model.AccessTokenScope + expectedAuthStatusCode int + expectedStatusCode int + }{ + {"No Package permission", auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusUnauthorized}, + {"Package Read permission", auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized}, + {"Package Write permission", auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusCreated}, + {"All permission", auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusCreated}, + } { + t.Run(permission.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - testCase(t, auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusUnauthorized) - }) - - t.Run("Package Read permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized) - }) - - t.Run("Package Write permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusCreated) - }) - - t.Run("All permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusCreated) - }) + testCase(t, permission.scope, permission.expectedAuthStatusCode, permission.expectedStatusCode) + }) + } }) t.Run("CheckCredentials", func(t *testing.T) { @@ -657,19 +626,17 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1) - req := NewRequest(t, "GET", recipeURL+"/latest") - resp := MakeRequest(t, req, http.StatusOK) + for _, requestURL := range []string{ + recipeURL + "/latest", + fmt.Sprintf("%s/revisions/%s/packages/%s/latest", recipeURL, revision1, conanPackageReference), + } { + req := NewRequest(t, "GET", requestURL) + resp := MakeRequest(t, req, http.StatusOK) - obj := DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, obj, "revision") - assert.Equal(t, revision1, obj["revision"]) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/revisions/%s/packages/%s/latest", recipeURL, revision1, conanPackageReference)) - resp = MakeRequest(t, req, http.StatusOK) - - obj = DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, obj, "revision") - assert.Equal(t, revision1, obj["revision"]) + obj := DecodeJSON(t, resp, map[string]string{}) + assert.Contains(t, obj, "revision") + assert.Equal(t, revision1, obj["revision"]) + } }) t.Run("ListRevisions", func(t *testing.T) { @@ -681,9 +648,6 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions", url, name, version1, user1, channel1) - req := NewRequest(t, "GET", recipeURL) - resp := MakeRequest(t, req, http.StatusOK) - type RevisionInfo struct { Revision string `json:"revision"` Time time.Time `json:"time"` @@ -693,24 +657,21 @@ func TestPackageConan(t *testing.T) { Revisions []*RevisionInfo `json:"revisions"` } - list := DecodeJSON(t, resp, &RevisionList{}) - assert.Len(t, list.Revisions, 2) - revs := make([]string, 0, len(list.Revisions)) - for _, rev := range list.Revisions { - revs = append(revs, rev.Revision) - } - assert.ElementsMatch(t, []string{revision1, revision2}, revs) + for _, requestURL := range []string{ + recipeURL, + fmt.Sprintf("%s/%s/packages/%s/revisions", recipeURL, revision1, conanPackageReference), + } { + req := NewRequest(t, "GET", requestURL) + resp := MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/packages/%s/revisions", recipeURL, revision1, conanPackageReference)) - resp = MakeRequest(t, req, http.StatusOK) - - list = DecodeJSON(t, resp, &RevisionList{}) - assert.Len(t, list.Revisions, 2) - revs = make([]string, 0, len(list.Revisions)) - for _, rev := range list.Revisions { - revs = append(revs, rev.Revision) + list := DecodeJSON(t, resp, &RevisionList{}) + assert.Len(t, list.Revisions, 2) + revs := make([]string, 0, len(list.Revisions)) + for _, rev := range list.Revisions { + revs = append(revs, rev.Revision) + } + assert.ElementsMatch(t, []string{revision1, revision2}, revs) } - assert.ElementsMatch(t, []string{revision1, revision2}, revs) }) t.Run("Search", func(t *testing.T) { @@ -753,27 +714,33 @@ func TestPackageConan(t *testing.T) { t.Run("Package", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/search", url, name, version1, user1, channel1)) - resp := MakeRequest(t, req, http.StatusOK) + for _, requestURL := range []string{ + fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/search", url, name, version1, user1, channel1), + fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/search", url, name, version1, user1, channel1, revision1), + } { + req := NewRequest(t, "GET", requestURL) + resp := MakeRequest(t, req, http.StatusOK) - result := DecodeJSON(t, resp, map[string]*conan_module.Conaninfo{}) + result := DecodeJSON(t, resp, map[string]*conan_module.Conaninfo{}) - assert.Contains(t, result, conanPackageReference) - info := result[conanPackageReference] - assert.NotEmpty(t, info.Settings) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/search", url, name, version1, user1, channel1, revision1)) - resp = MakeRequest(t, req, http.StatusOK) - - result = DecodeJSON(t, resp, map[string]*conan_module.Conaninfo{}) - - assert.Contains(t, result, conanPackageReference) - info = result[conanPackageReference] - assert.NotEmpty(t, info.Settings) + assert.Contains(t, result, conanPackageReference) + info := result[conanPackageReference] + assert.NotEmpty(t, info.Settings) + } }) }) t.Run("Delete", func(t *testing.T) { + deleteConanResource := func(t *testing.T, requestURL string) { + req := NewRequest(t, "DELETE", requestURL). + AddTokenAuth(badToken) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", requestURL). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + } + t.Run("Package", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -793,23 +760,11 @@ func TestPackageConan(t *testing.T) { checkPackageRevisionCount(2) - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)) checkPackageRevisionCount(1) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)) checkPackageRevisionCount(0) @@ -817,13 +772,7 @@ func TestPackageConan(t *testing.T) { checkPackageReferenceCount(1) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)) checkPackageReferenceCount(0) }) @@ -841,23 +790,11 @@ func TestPackageConan(t *testing.T) { checkRecipeRevisionCount(2) - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)) checkRecipeRevisionCount(1) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)) checkRecipeRevisionCount(0) }) diff --git a/tests/integration/api_packages_conda_test.go b/tests/integration/api_packages_conda_test.go index 23f88abff4b..a301034ec3f 100644 --- a/tests/integration/api_packages_conda_test.go +++ b/tests/integration/api_packages_conda_test.go @@ -62,16 +62,13 @@ func TestPackageConda(t *testing.T) { filename := fmt.Sprintf("%s-%s.tar.bz2", packageName, packageVersion) - req := NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - - req = NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) + for _, status := range []int{http.StatusUnauthorized, http.StatusCreated, http.StatusConflict} { + req := NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())) + if status != http.StatusUnauthorized { + req.AddBasicAuth(user.Name) + } + MakeRequest(t, req, status) + } pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeConda) assert.NoError(t, err) @@ -101,16 +98,13 @@ func TestPackageConda(t *testing.T) { fullName := channel + "/" + packageName filename := fmt.Sprintf("%s-%s.conda", packageName, packageVersion) - req := NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - - req = NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) + for _, status := range []int{http.StatusUnauthorized, http.StatusCreated, http.StatusConflict} { + req := NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())) + if status != http.StatusUnauthorized { + req.AddBasicAuth(user.Name) + } + MakeRequest(t, req, status) + } pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeConda) assert.NoError(t, err) @@ -190,84 +184,60 @@ func TestPackageConda(t *testing.T) { Removed map[string]*PackageInfo `json:"removed"` } - req := NewRequest(t, "GET", root+"/noarch/repodata.json") - resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) - - req = NewRequest(t, "GET", root+"/noarch/repodata.json.bz2") - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) - - req = NewRequest(t, "GET", root+"/noarch/current_repodata.json") - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) - - req = NewRequest(t, "GET", root+"/noarch/current_repodata.json.bz2") - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) - - t.Run(".tar.bz2", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - pv, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeConda, packageName, packageVersion) - assert.NoError(t, err) - - pd, err := packages.GetPackageDescriptor(t.Context(), pv) - assert.NoError(t, err) - - req := NewRequest(t, "GET", root+"/noarch/repodata.json") + for _, c := range []struct { + filename, contentType string + }{ + {"repodata.json", "application/json"}, + {"repodata.json.bz2", "application/x-bzip2"}, + {"current_repodata.json", "application/json"}, + {"current_repodata.json.bz2", "application/x-bzip2"}, + } { + req := NewRequest(t, "GET", root+"/noarch/"+c.filename) resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, c.contentType, resp.Header().Get("Content-Type")) + } - result := DecodeJSON(t, resp, &RepoData{}) + for _, c := range []struct { + extension, name, root string + }{ + {".tar.bz2", packageName, root}, + {".conda", channel + "/" + packageName, root + "/" + channel}, + } { + t.Run(c.extension, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - assert.Equal(t, "noarch", result.Info.Subdir) - assert.Empty(t, result.PackagesConda) - assert.Empty(t, result.Removed) + pv, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeConda, c.name, packageVersion) + assert.NoError(t, err) - filename := fmt.Sprintf("%s-%s-xxx.tar.bz2", packageName, packageVersion) - assert.Contains(t, result.Packages, filename) - packageInfo := result.Packages[filename] - assert.Equal(t, packageName, packageInfo.Name) - assert.Equal(t, packageVersion, packageInfo.Version) - assert.Equal(t, "noarch", packageInfo.Subdir) - assert.Equal(t, "xxx", packageInfo.Build) - assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) - assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) - assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) - assert.NotNil(t, packageInfo.Dependencies) - assert.Empty(t, packageInfo.Dependencies) - }) + pd, err := packages.GetPackageDescriptor(t.Context(), pv) + assert.NoError(t, err) - t.Run(".conda", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + req := NewRequest(t, "GET", c.root+"/noarch/repodata.json") + resp := MakeRequest(t, req, http.StatusOK) - pv, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeConda, channel+"/"+packageName, packageVersion) - assert.NoError(t, err) + result := DecodeJSON(t, resp, &RepoData{}) + packageInfos, otherPackages := result.Packages, result.PackagesConda + if c.extension == ".conda" { + packageInfos, otherPackages = result.PackagesConda, result.Packages + } - pd, err := packages.GetPackageDescriptor(t.Context(), pv) - assert.NoError(t, err) + assert.Equal(t, "noarch", result.Info.Subdir) + assert.Empty(t, otherPackages) + assert.Empty(t, result.Removed) - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/noarch/repodata.json", root, channel)) - resp := MakeRequest(t, req, http.StatusOK) - - result := DecodeJSON(t, resp, &RepoData{}) - - assert.Equal(t, "noarch", result.Info.Subdir) - assert.Empty(t, result.Packages) - assert.Empty(t, result.Removed) - - filename := fmt.Sprintf("%s-%s-xxx.conda", packageName, packageVersion) - assert.Contains(t, result.PackagesConda, filename) - packageInfo := result.PackagesConda[filename] - assert.Equal(t, packageName, packageInfo.Name) - assert.Equal(t, packageVersion, packageInfo.Version) - assert.Equal(t, "noarch", packageInfo.Subdir) - assert.Equal(t, "xxx", packageInfo.Build) - assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) - assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) - assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) - assert.NotNil(t, packageInfo.Dependencies) - assert.Empty(t, packageInfo.Dependencies) - }) + filename := fmt.Sprintf("%s-%s-xxx%s", packageName, packageVersion, c.extension) + assert.Contains(t, packageInfos, filename) + packageInfo := packageInfos[filename] + assert.Equal(t, packageName, packageInfo.Name) + assert.Equal(t, packageVersion, packageInfo.Version) + assert.Equal(t, "noarch", packageInfo.Subdir) + assert.Equal(t, "xxx", packageInfo.Build) + assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) + assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) + assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) + assert.NotNil(t, packageInfo.Dependencies) + assert.Empty(t, packageInfo.Dependencies) + }) + } }) } diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index df59c246692..6d2c02658fa 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -199,17 +199,14 @@ func TestPackageContainer(t *testing.T) { t.Run("DetermineSupport", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + for _, token := range []string{userToken, readToken} { + req := NewRequest(t, "GET", setting.AppURL+"v2"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) + } + req := NewRequest(t, "GET", setting.AppURL+"v2"). - AddTokenAuth(userToken) - resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) - - req = NewRequest(t, "GET", setting.AppURL+"v2"). - AddTokenAuth(readToken) - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) - - req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(badToken) MakeRequest(t, req, http.StatusUnauthorized) }) @@ -221,19 +218,13 @@ func TestPackageContainer(t *testing.T) { t.Run("UploadBlob/Monolithic", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(anonymousToken) - MakeRequest(t, req, http.StatusUnauthorized) + for _, token := range []string{anonymousToken, readToken, badToken} { + req := NewRequest(t, "POST", url+"/blobs/uploads"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnauthorized) + } - req = NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(readToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, unknownDigest), bytes.NewReader(blobContent)). + req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, unknownDigest), bytes.NewReader(blobContent)). AddTokenAuth(userToken) MakeRequest(t, req, http.StatusBadRequest) @@ -259,15 +250,13 @@ func TestPackageContainer(t *testing.T) { t.Run("UploadBlob/Chunked", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + for _, token := range []string{readToken, badToken} { + req := NewRequest(t, "POST", url+"/blobs/uploads"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnauthorized) + } + req := NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(readToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusAccepted) @@ -714,41 +703,25 @@ func TestPackageContainer(t *testing.T) { }) t.Run("Delete", func(t *testing.T) { - t.Run("Blob", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, c := range []struct { + name, url string + }{ + {"Blob", fmt.Sprintf("%s/blobs/%s", url, blobDigest)}, + {"ManifestByDigest", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)}, + {"ManifestByTag", fmt.Sprintf("%s/manifests/%s", url, multiTag)}, + } { + t.Run(c.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/blobs/%s", url, blobDigest)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusAccepted) + req := NewRequest(t, "DELETE", c.url). + AddTokenAuth(userToken) + MakeRequest(t, req, http.StatusAccepted) - req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("ManifestByDigest", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusAccepted) - - req = NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("ManifestByTag", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/manifests/%s", url, multiTag)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusAccepted) - - req = NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/%s", url, multiTag)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusNotFound) - }) + req = NewRequest(t, "HEAD", c.url). + AddTokenAuth(userToken) + MakeRequest(t, req, http.StatusNotFound) + }) + } }) }) } diff --git a/tests/integration/api_packages_cran_test.go b/tests/integration/api_packages_cran_test.go index 0615a7a1e9d..8ad38bfceea 100644 --- a/tests/integration/api_packages_cran_test.go +++ b/tests/integration/api_packages_cran_test.go @@ -44,6 +44,24 @@ func TestPackageCran(t *testing.T) { url := fmt.Sprintf("/api/packages/%s/cran", user.Name) + enumerate := func(t *testing.T, indexURL string) { + req := NewRequest(t, "GET", indexURL). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") + + body := resp.Body.String() + assert.Contains(t, body, "Package: "+packageName) + assert.Contains(t, body, "Version: "+packageVersion) + + req = NewRequest(t, "GET", indexURL+".gz"). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Contains(t, resp.Header().Get("Content-Type"), "application/x-gzip") + } + t.Run("Source", func(t *testing.T) { t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -105,21 +123,7 @@ func TestPackageCran(t *testing.T) { t.Run("Enumerate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", url+"/src/contrib/PACKAGES"). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") - - body := resp.Body.String() - assert.Contains(t, body, "Package: "+packageName) - assert.Contains(t, body, "Version: "+packageVersion) - - req = NewRequest(t, "GET", url+"/src/contrib/PACKAGES.gz"). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Contains(t, resp.Header().Get("Content-Type"), "application/x-gzip") + enumerate(t, url+"/src/contrib/PACKAGES") }) }) @@ -189,21 +193,7 @@ func TestPackageCran(t *testing.T) { req := NewRequest(t, "GET", url+"/bin/windows/contrib/4.1/PACKAGES") MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", url+"/bin/windows/contrib/4.2/PACKAGES"). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") - - body := resp.Body.String() - assert.Contains(t, body, "Package: "+packageName) - assert.Contains(t, body, "Version: "+packageVersion) - - req = NewRequest(t, "GET", url+"/bin/windows/contrib/4.2/PACKAGES.gz"). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Contains(t, resp.Header().Get("Content-Type"), "application/x-gzip") + enumerate(t, url+"/bin/windows/contrib/4.2/PACKAGES") }) }) diff --git a/tests/integration/api_packages_goproxy_test.go b/tests/integration/api_packages_goproxy_test.go index 615aaed5327..84925ddd715 100644 --- a/tests/integration/api_packages_goproxy_test.go +++ b/tests/integration/api_packages_goproxy_test.go @@ -98,52 +98,43 @@ func TestPackageGo(t *testing.T) { t.Run("Info", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.info", url, packageName, packageVersion)) - resp := MakeRequest(t, req, http.StatusOK) - type Info struct { Version string `json:"Version"` Time time.Time `json:"Time"` } - info := DecodeJSON(t, resp, &Info{}) + for _, infoRequest := range []struct { + path, version string + }{ + {"@v/" + packageVersion + ".info", packageVersion}, + {"@v/latest.info", packageVersion2}, + {"@latest", packageVersion2}, + } { + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", url, packageName, infoRequest.path)) + resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, packageVersion, info.Version) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.info", url, packageName)) - resp = MakeRequest(t, req, http.StatusOK) - - info = DecodeJSON(t, resp, &Info{}) - assert.Equal(t, packageVersion2, info.Version) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@latest", url, packageName)) - resp = MakeRequest(t, req, http.StatusOK) - - info = DecodeJSON(t, resp, &Info{}) - assert.Equal(t, packageVersion2, info.Version) + info := DecodeJSON(t, resp, &Info{}) + assert.Equal(t, infoRequest.version, info.Version) + } }) t.Run("GoMod", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.mod", url, packageName, packageVersion)) - resp := MakeRequest(t, req, http.StatusOK) + for _, version := range []string{packageVersion, "latest"} { + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.mod", url, packageName, version)) + resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, goModContent, resp.Body.String()) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.mod", url, packageName)) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, goModContent, resp.Body.String()) + assert.Equal(t, goModContent, resp.Body.String()) + } }) t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.zip", url, packageName, packageVersion)) - MakeRequest(t, req, http.StatusOK) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.zip", url, packageName)) - MakeRequest(t, req, http.StatusOK) + for _, version := range []string{packageVersion, "latest"} { + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.zip", url, packageName, version)) + MakeRequest(t, req, http.StatusOK) + } }) } diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go index df25f780f46..e011542bad0 100644 --- a/tests/integration/api_packages_maven_test.go +++ b/tests/integration/api_packages_maven_test.go @@ -47,6 +47,22 @@ func TestPackageMaven(t *testing.T) { assert.NotEmpty(t, h.Get("Last-Modified")) } + checkDownload := func(t *testing.T, name, contentType, content string, downloadCount int64) { + for _, method := range []string{"HEAD", "GET"} { + req := NewRequest(t, method, fmt.Sprintf("%s/%s/%s", root, packageVersion, name)).AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + checkHeaders(t, resp.Header(), contentType, int64(len(content))) + if method == "GET" { + assert.Equal(t, []byte(content), resp.Body.Bytes()) + } + } + + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeMaven) + require.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, downloadCount, pvs[0].DownloadCount) + } + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -139,22 +155,7 @@ func TestPackageMaven(t *testing.T) { t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "HEAD", fmt.Sprintf("%s/%s/%s", root, packageVersion, filename)).AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - checkHeaders(t, resp.Header(), "application/java-archive", 4) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", root, packageVersion, filename)).AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - checkHeaders(t, resp.Header(), "application/java-archive", 4) - - assert.Equal(t, []byte("test"), resp.Body.Bytes()) - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeMaven) - require.NoError(t, err) - assert.Len(t, pvs, 1) - assert.Equal(t, int64(0), pvs[0].DownloadCount) + checkDownload(t, filename, "application/java-archive", "test", 0) }) t.Run("UploadVerifySHA1", func(t *testing.T) { @@ -219,22 +220,7 @@ func TestPackageMaven(t *testing.T) { t.Run("DownloadPOM", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "HEAD", fmt.Sprintf("%s/%s/%s.pom", root, packageVersion, filename)).AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - checkHeaders(t, resp.Header(), "text/xml", int64(len(pomContent))) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.pom", root, packageVersion, filename)).AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - checkHeaders(t, resp.Header(), "text/xml", int64(len(pomContent))) - - assert.Equal(t, []byte(pomContent), resp.Body.Bytes()) - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeMaven) - require.NoError(t, err) - assert.Len(t, pvs, 1) - assert.Equal(t, int64(1), pvs[0].DownloadCount) + checkDownload(t, filename+".pom", "text/xml", pomContent, 1) }) t.Run("DownloadChecksums", func(t *testing.T) { diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index f9ccc6a5c4e..a8d60a35106 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -164,210 +164,147 @@ func TestPackageNuGet(t *testing.T) { url := fmt.Sprintf("/api/packages/%s/nuget", user.Name) t.Run("ServiceIndex", func(t *testing.T) { - t.Run("v2", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, version := range []string{"v2", "v3"} { + t.Run(version, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}) + privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}) - cases := []struct { - Owner string - UseBasicAuth bool - token string - expectedStatus int - }{ - {privateUser.Name, false, "", http.StatusOK}, - {privateUser.Name, true, "", http.StatusOK}, - {privateUser.Name, false, writeToken, http.StatusOK}, - {privateUser.Name, false, readToken, http.StatusOK}, - {privateUser.Name, false, badToken, http.StatusOK}, - {user.Name, false, "", http.StatusOK}, - {user.Name, true, "", http.StatusOK}, - {user.Name, false, writeToken, http.StatusOK}, - {user.Name, false, readToken, http.StatusOK}, - {user.Name, false, badToken, http.StatusOK}, - } + cases := []struct { + Owner string + UseBasicAuth bool + token string + expectedStatus int + }{ + {privateUser.Name, false, "", http.StatusOK}, + {privateUser.Name, true, "", http.StatusOK}, + {privateUser.Name, false, writeToken, http.StatusOK}, + {privateUser.Name, false, readToken, http.StatusOK}, + {privateUser.Name, false, badToken, http.StatusOK}, + {user.Name, false, "", http.StatusOK}, + {user.Name, true, "", http.StatusOK}, + {user.Name, false, writeToken, http.StatusOK}, + {user.Name, false, readToken, http.StatusOK}, + {user.Name, false, badToken, http.StatusOK}, + } - for _, c := range cases { - t.Run(c.Owner, func(t *testing.T) { - url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) + for _, c := range cases { + t.Run(c.Owner, func(t *testing.T) { + url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) - req := NewRequest(t, "GET", url) - if c.UseBasicAuth { - req.AddBasicAuth(user.Name) - } else if c.token != "" { - addNuGetAPIKeyHeader(req, c.token) - } - resp := MakeRequest(t, req, c.expectedStatus) - if c.expectedStatus != http.StatusOK { - return - } - - var result nuget.ServiceIndexResponseV2 - decodeXML(t, resp, &result) - - assert.Equal(t, setting.AppURL+url[1:], result.Base) - assert.Equal(t, "Packages", result.Workspace.Collection.Href) - }) - } - }) - - t.Run("v3", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}) - - cases := []struct { - Owner string - UseBasicAuth bool - token string - expectedStatus int - }{ - {privateUser.Name, false, "", http.StatusOK}, - {privateUser.Name, true, "", http.StatusOK}, - {privateUser.Name, false, writeToken, http.StatusOK}, - {privateUser.Name, false, readToken, http.StatusOK}, - {privateUser.Name, false, badToken, http.StatusOK}, - {user.Name, false, "", http.StatusOK}, - {user.Name, true, "", http.StatusOK}, - {user.Name, false, writeToken, http.StatusOK}, - {user.Name, false, readToken, http.StatusOK}, - {user.Name, false, badToken, http.StatusOK}, - } - - for _, c := range cases { - t.Run(c.Owner, func(t *testing.T) { - url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) - - req := NewRequest(t, "GET", url+"/index.json") - if c.UseBasicAuth { - req.AddBasicAuth(user.Name) - } else if c.token != "" { - addNuGetAPIKeyHeader(req, c.token) - } - resp := MakeRequest(t, req, c.expectedStatus) - - if c.expectedStatus != http.StatusOK { - return - } - - result := DecodeJSON(t, resp, &nuget.ServiceIndexResponseV3{}) - - assert.Equal(t, "3.0.0", result.Version) - assert.NotEmpty(t, result.Resources) - - root := setting.AppURL + url[1:] - for _, r := range result.Resources { - switch r.Type { - case "SearchQueryService": - fallthrough - case "SearchQueryService/3.0.0-beta": - fallthrough - case "SearchQueryService/3.0.0-rc": - assert.Equal(t, root+"/query", r.ID) - case "RegistrationsBaseUrl": - fallthrough - case "RegistrationsBaseUrl/3.0.0-beta": - fallthrough - case "RegistrationsBaseUrl/3.0.0-rc": - assert.Equal(t, root+"/registration", r.ID) - case "PackageBaseAddress/3.0.0": - assert.Equal(t, root+"/package", r.ID) - case "PackagePublish/2.0.0": - assert.Equal(t, root, r.ID) + endpoint := url + if version == "v3" { + endpoint += "/index.json" } - } - }) - } - }) + req := NewRequest(t, "GET", endpoint) + if c.UseBasicAuth { + req.AddBasicAuth(user.Name) + } else if c.token != "" { + addNuGetAPIKeyHeader(req, c.token) + } + resp := MakeRequest(t, req, c.expectedStatus) + if c.expectedStatus != http.StatusOK { + return + } + + if version == "v2" { + var result nuget.ServiceIndexResponseV2 + decodeXML(t, resp, &result) + + assert.Equal(t, setting.AppURL+url[1:], result.Base) + assert.Equal(t, "Packages", result.Workspace.Collection.Href) + } else { + result := DecodeJSON(t, resp, &nuget.ServiceIndexResponseV3{}) + + assert.Equal(t, "3.0.0", result.Version) + assert.NotEmpty(t, result.Resources) + + root := setting.AppURL + url[1:] + for _, r := range result.Resources { + switch r.Type { + case "SearchQueryService": + fallthrough + case "SearchQueryService/3.0.0-beta": + fallthrough + case "SearchQueryService/3.0.0-rc": + assert.Equal(t, root+"/query", r.ID) + case "RegistrationsBaseUrl": + fallthrough + case "RegistrationsBaseUrl/3.0.0-beta": + fallthrough + case "RegistrationsBaseUrl/3.0.0-rc": + assert.Equal(t, root+"/registration", r.ID) + case "PackageBaseAddress/3.0.0": + assert.Equal(t, root+"/package", r.ID) + case "PackagePublish/2.0.0": + assert.Equal(t, root, r.ID) + } + } + } + }) + } + }) + } }) t.Run("Upload", func(t *testing.T) { t.Run("DependencyPackage", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - // create with username/password - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + for _, useToken := range []bool{false, true} { + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) + if useToken { + // create with token + req.AddTokenAuth(writeToken) + } else { + // create with username/password + req.AddBasicAuth(user.Name) + } + MakeRequest(t, req, http.StatusCreated) - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeNuGet) - assert.NoError(t, err) - assert.Len(t, pvs, 1, "Should have one version") + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeNuGet) + assert.NoError(t, err) + assert.Len(t, pvs, 1, "Should have one version") - pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) - assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) + pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) - pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 2, "Should have 2 files: nuget and nuspec") - for _, pf := range pfs { - switch pf.Name { - case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion): - assert.True(t, pf.IsLead) + pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 2, "Should have 2 files: nuget and nuspec") + for _, pf := range pfs { + switch pf.Name { + case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion): + assert.True(t, pf.IsLead) - pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) - assert.NoError(t, err) - assert.Equal(t, int64(len(content)), pb.Size) - case packageName + ".nuspec": - assert.False(t, pf.IsLead) - default: - assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) + pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(len(content)), pb.Size) + case packageName + ".nuspec": + assert.False(t, pf.IsLead) + default: + assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) + } + } + + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusConflict) + + if !useToken { + // delete the package + assert.NoError(t, packageService.DeletePackageVersionAndReferences(t.Context(), pvs[0])) + + // create failure with token without write access + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). + AddTokenAuth(readToken) + MakeRequest(t, req, http.StatusUnauthorized) } } - - req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) - - // delete the package - assert.NoError(t, packageService.DeletePackageVersionAndReferences(t.Context(), pvs[0])) - - // create failure with token without write access - req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddTokenAuth(readToken) - MakeRequest(t, req, http.StatusUnauthorized) - - // create with token - req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusCreated) - - pvs, err = packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeNuGet) - assert.NoError(t, err) - assert.Len(t, pvs, 1, "Should have one version") - - pd, err = packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) - assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) - - pfs, err = packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 2, "Should have 2 files: nuget and nuspec") - for _, pf := range pfs { - switch pf.Name { - case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion): - assert.True(t, pf.IsLead) - - pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) - assert.NoError(t, err) - assert.Equal(t, int64(len(content)), pb.Size) - case packageName + ".nuspec": - assert.False(t, pf.IsLead) - default: - assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) - } - } - - req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) }) t.Run("SymbolPackage", func(t *testing.T) { @@ -561,11 +498,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) } t.Run("v2", func(t *testing.T) { - t.Run("Search()", func(t *testing.T) { + testSearch := func(t *testing.T, endpoint, query string) { defer tests.PrintCurrentTest(t)() for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='%s'&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s?%s&$skip=%d&$top=%d", url, endpoint, fmt.Sprintf(query, c.Query), c.Skip, c.Take)). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -575,59 +512,27 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) - req = NewRequest(t, "GET", fmt.Sprintf("%s/Search()/$count?searchTerm='%s'&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/$count?%s&$skip=%d&$top=%d", url, endpoint, fmt.Sprintf(query, c.Query), c.Skip, c.Take)). AddBasicAuth(user.Name) resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) } + } + + t.Run("Search()", func(t *testing.T) { + testSearch(t, "Search()", "searchTerm='%s'") }) t.Run("Packages()", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - var result FeedResponse - decodeXML(t, resp, &result) - - assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) - assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) - } + testSearch(t, "Packages()", "$filter=substringof('%s',tolower(Id))") }) t.Run("Packages()", func(t *testing.T) { defer tests.PrintCurrentTest(t)() t.Run("substringof", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - var result FeedResponse - decodeXML(t, resp, &result) - - assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) - assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) - } + testSearch(t, "Packages()", "$filter=substringof('%s',tolower(Id))") }) t.Run("IdEq", func(t *testing.T) { diff --git a/tests/integration/api_packages_pub_test.go b/tests/integration/api_packages_pub_test.go index 072a36978d9..314109d6cd4 100644 --- a/tests/integration/api_packages_pub_test.go +++ b/tests/integration/api_packages_pub_test.go @@ -117,19 +117,19 @@ description: ` + packageDescription, uploadFile(t, result.URL, buildPackage(packageVersionLatest), http.StatusNoContent) }) + type VersionMetadata struct { + Version string `json:"version"` + ArchiveURL string `json:"archive_url"` + Published time.Time `json:"published"` + Pubspec any `json:"pubspec,omitempty"` + } + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", fmt.Sprintf("%s/api/packages/%s/%s", root, packageName, packageVersion)) resp := MakeRequest(t, req, http.StatusOK) - type VersionMetadata struct { - Version string `json:"version"` - ArchiveURL string `json:"archive_url"` - Published time.Time `json:"published"` - Pubspec any `json:"pubspec,omitempty"` - } - result := DecodeJSON(t, resp, &VersionMetadata{}) assert.Equal(t, packageVersion, result.Version) @@ -147,13 +147,6 @@ description: ` + packageDescription, req := NewRequest(t, "GET", fmt.Sprintf("%s/api/packages/%s", root, packageName)) resp := MakeRequest(t, req, http.StatusOK) - type VersionMetadata struct { - Version string `json:"version"` - ArchiveURL string `json:"archive_url"` - Published time.Time `json:"published"` - Pubspec any `json:"pubspec,omitempty"` - } - type PackageVersions struct { Name string `json:"name"` Latest *VersionMetadata `json:"latest"` diff --git a/tests/integration/api_packages_pypi_test.go b/tests/integration/api_packages_pypi_test.go index a24d6c96eb1..a05f906d208 100644 --- a/tests/integration/api_packages_pypi_test.go +++ b/tests/integration/api_packages_pypi_test.go @@ -74,66 +74,55 @@ func TestPackagePyPI(t *testing.T) { uploadHelper(t, body, writer.FormDataContentType(), expectedStatus) } - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, upload := range []struct { + name string + filename string + fileCount int + }{ + {"Upload", "test.whl", 1}, + {"UploadAddFile", "test.tar.gz", 2}, + } { + t.Run(upload.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - filename := "test.whl" - uploadFile(t, filename, content, http.StatusCreated) + uploadFile(t, upload.filename, content, http.StatusCreated) - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypePyPI) - assert.NoError(t, err) - assert.Len(t, pvs, 1) + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypePyPI) + assert.NoError(t, err) + assert.Len(t, pvs, 1) - pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.Nil(t, pd.SemVer) - metadata, ok := pd.Metadata.(*pypi.Metadata) - require.True(t, ok) - assert.Equal(t, projectURL, metadata.ProjectURL) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) + pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) + assert.NoError(t, err) + assert.Nil(t, pd.SemVer) + if upload.fileCount == 1 { + metadata, ok := pd.Metadata.(*pypi.Metadata) + require.True(t, ok) + assert.Equal(t, projectURL, metadata.ProjectURL) + } else { + assert.IsType(t, &pypi.Metadata{}, pd.Metadata) + } + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) - pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 1) - assert.Equal(t, filename, pfs[0].Name) - assert.True(t, pfs[0].IsLead) + pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, upload.fileCount) - pb, err := packages.GetBlobByID(t.Context(), pfs[0].BlobID) - assert.NoError(t, err) - assert.Equal(t, int64(4), pb.Size) - }) + var pf *packages.PackageFile + if upload.fileCount == 1 { + pf = pfs[0] + } else { + pf, err = packages.GetFileForVersionByName(t.Context(), pvs[0].ID, upload.filename, packages.EmptyFileKey) + assert.NoError(t, err) + } + assert.Equal(t, upload.filename, pf.Name) + assert.True(t, pf.IsLead) - t.Run("UploadAddFile", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - filename := "test.tar.gz" - uploadFile(t, filename, content, http.StatusCreated) - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypePyPI) - assert.NoError(t, err) - assert.Len(t, pvs, 1) - - pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.Nil(t, pd.SemVer) - assert.IsType(t, &pypi.Metadata{}, pd.Metadata) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) - - pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 2) - - pf, err := packages.GetFileForVersionByName(t.Context(), pvs[0].ID, filename, packages.EmptyFileKey) - assert.NoError(t, err) - assert.Equal(t, filename, pf.Name) - assert.True(t, pf.IsLead) - - pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) - assert.NoError(t, err) - assert.Equal(t, int64(4), pb.Size) - }) + pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(4), pb.Size) + }) + } t.Run("UploadHashMismatch", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 639fb0e37e5..cf7e9ef3553 100644 --- a/tests/integration/api_packages_rpm_test.go +++ b/tests/integration/api_packages_rpm_test.go @@ -467,21 +467,42 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, }, } - t.Run("Success", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - updates := []*rpm_module.Update{&advisory} + postAdvisories := func(t *testing.T, url string, updates []*rpm_module.Update, expectedStatus int) { body, err := json.Marshal(updates) assert.NoError(t, err) - req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)). + req := NewRequestWithBody(t, "POST", url, bytes.NewReader(body)). AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + MakeRequest(t, req, expectedStatus) + } + + getUpdates := func(t *testing.T) updateInfo { + req := NewRequest(t, "GET", groupURL+"/repodata/updateinfo.xml.gz") + resp := MakeRequest(t, req, http.StatusOK) + + var result updateInfo + decodeGzipXML(t, resp, &result) + return result + } + + findUpdate := func(updates []*rpm_module.Update, id string) *rpm_module.Update { + for _, update := range updates { + if update.ID == id { + return update + } + } + return nil + } + + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + postAdvisories(t, errataURL, []*rpm_module.Update{&advisory}, http.StatusOK) url := groupURL + "/repodata" // Check repomd.xml contains updateinfo - req = NewRequest(t, "GET", url+"/repomd.xml") + req := NewRequest(t, "GET", url+"/repomd.xml") resp := MakeRequest(t, req, http.StatusOK) type testRepoData struct { @@ -490,7 +511,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, var repomd struct { Data []*testRepoData `xml:"data"` } - err = xml.NewDecoder(resp.Body).Decode(&repomd) + err := xml.NewDecoder(resp.Body).Decode(&repomd) require.NoError(t, err) found := slices.IndexFunc(repomd.Data, func(s *testRepoData) bool { @@ -499,11 +520,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, assert.True(t, found, "updateinfo not found in repomd.xml") // Now check updateinfo.xml.gz - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp = MakeRequest(t, req, http.StatusOK) - - var result updateInfo - decodeGzipXML(t, resp, &result) + result := getUpdates(t) assert.Equal(t, "http://linux.duke.edu/metadata/updateinfo", result.Xmlns) assert.Len(t, result.Updates, 1) @@ -559,21 +576,10 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, MakeRequest(t, req, http.StatusOK) // Verify it was stored correctly (skipping nulls) - url := groupURL + "/repodata" - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp := MakeRequest(t, req, http.StatusOK) - - var result updateInfo - decodeGzipXML(t, resp, &result) + result := getUpdates(t) // We need to find the new advisory CVE-2023-5678 - var newAdvisory *rpm_module.Update - for _, u := range result.Updates { - if u.ID == "CVE-2023-5678" { - newAdvisory = u - break - } - } + newAdvisory := findUpdate(result.Updates, "CVE-2023-5678") assert.NotNil(t, newAdvisory) assert.Len(t, newAdvisory.PkgList, 1) assert.Equal(t, "el9", newAdvisory.PkgList[0].Short) @@ -585,13 +591,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, defer tests.PrintCurrentTest(t)() badURL := fmt.Sprintf("%s/package/%s/non-existent-version/errata", groupURL, packageName) - updates := []*rpm_module.Update{&advisory} - body, err := json.Marshal(updates) - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", badURL, bytes.NewReader(body)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusNotFound) + postAdvisories(t, badURL, []*rpm_module.Update{&advisory}, http.StatusNotFound) }) t.Run("MergeAdvisories", func(t *testing.T) { @@ -615,29 +615,12 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, }, } - updates := []*rpm_module.Update{&advisory2} - body, err := json.Marshal(updates) - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + postAdvisories(t, errataURL, []*rpm_module.Update{&advisory2}, http.StatusOK) // Check updateinfo.xml.gz again - url := groupURL + "/repodata" - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp := MakeRequest(t, req, http.StatusOK) + result := getUpdates(t) - var result updateInfo - decodeGzipXML(t, resp, &result) - - var targetUpdate *rpm_module.Update - for _, u := range result.Updates { - if u.ID == "CVE-2023-1234" { - targetUpdate = u - break - } - } + targetUpdate := findUpdate(result.Updates, "CVE-2023-1234") assert.NotNil(t, targetUpdate) // Verify that package lists are merged into the same collection assert.Len(t, targetUpdate.PkgList, 1) @@ -651,44 +634,15 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, // Upload a third advisory with the same ID but a different collection advisory3 := advisory advisory3.PkgList = []*rpm_module.Collection{ - { - Short: "el8", - Packages: []*rpm_module.UpdatePackage{ - { - Arch: packageArchitecture, - Name: packageName, - Release: "1", - Src: "gitea-test-1.0.2-1.src.rpm", - Version: "1.0.2", - Filename: fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), - }, - }, - }, + {Short: "el8", Packages: advisory.PkgList[0].Packages}, } - updates := []*rpm_module.Update{&advisory3} - body, err := json.Marshal(updates) - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + postAdvisories(t, errataURL, []*rpm_module.Update{&advisory3}, http.StatusOK) // Check updateinfo.xml.gz again - url := groupURL + "/repodata" - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp := MakeRequest(t, req, http.StatusOK) + result := getUpdates(t) - var result updateInfo - decodeGzipXML(t, resp, &result) - - var targetUpdate *rpm_module.Update - for _, u := range result.Updates { - if u.ID == "CVE-2023-1234" { - targetUpdate = u - break - } - } + targetUpdate := findUpdate(result.Updates, "CVE-2023-1234") assert.NotNil(t, targetUpdate) // Verify that we now have 2 collections assert.Len(t, targetUpdate.PkgList, 2) @@ -716,20 +670,9 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, MakeRequest(t, req, http.StatusOK) // Check updateinfo.xml.gz - url := groupURL + "/repodata" - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp := MakeRequest(t, req, http.StatusOK) + result := getUpdates(t) - var result updateInfo - decodeGzipXML(t, resp, &result) - - var targetUpdate *rpm_module.Update - for _, u := range result.Updates { - if u.ID == "CVE-2023-1234" { - targetUpdate = u - break - } - } + targetUpdate := findUpdate(result.Updates, "CVE-2023-1234") assert.NotNil(t, targetUpdate) assert.Len(t, targetUpdate.PkgList, 2) diff --git a/tests/integration/api_packages_swift_test.go b/tests/integration/api_packages_swift_test.go index a6bb92402ec..07412db7168 100644 --- a/tests/integration/api_packages_swift_test.go +++ b/tests/integration/api_packages_swift_test.go @@ -64,19 +64,14 @@ func TestPackageSwift(t *testing.T) { t.Run("CheckLogin", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequestWithBody(t, "POST", url, strings.NewReader("")) - MakeRequest(t, req, http.StatusUnauthorized) + for _, endpoint := range []string{url, url + "/login"} { + req := NewRequestWithBody(t, "POST", endpoint, strings.NewReader("")) + MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequestWithBody(t, "POST", url, strings.NewReader("")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) - - req = NewRequestWithBody(t, "POST", url+"/login", strings.NewReader("")) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequestWithBody(t, "POST", url+"/login", strings.NewReader("")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + req = NewRequestWithBody(t, "POST", endpoint, strings.NewReader("")). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) + } }) t.Run("CheckAcceptMediaType", func(t *testing.T) { @@ -108,171 +103,122 @@ func TestPackageSwift(t *testing.T) { assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, upload := range []struct { + name, version string + formField bool + }{ + {"Upload", packageVersion, false}, + {"UploadMultipart", packageVersion2Beta, true}, + } { + t.Run(upload.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) { - var body bytes.Buffer - mpw := multipart.NewWriter(&body) + uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) { + var body bytes.Buffer + mpw := multipart.NewWriter(&body) - part, _ := mpw.CreateFormFile("source-archive", "source-archive.zip") - io.Copy(part, sr) + if upload.formField { + // Read the source archive content + sourceContent, err := io.ReadAll(sr) + assert.NoError(t, err) + mpw.WriteField("source-archive", string(sourceContent)) + } else { + part, _ := mpw.CreateFormFile("source-archive", "source-archive.zip") + io.Copy(part, sr) + } - if metadata != "" { - mpw.WriteField("metadata", metadata) + if metadata != "" { + mpw.WriteField("metadata", metadata) + } + + mpw.Close() + + req := NewRequestWithBody(t, "PUT", url, &body). + SetHeader("Content-Type", mpw.FormDataContentType()). + SetHeader("Accept", swift_router.AcceptJSON). + AddBasicAuth(user.Name) + MakeRequest(t, req, expectedStatus) } - mpw.Close() + if !upload.formField { + for _, triple := range []string{"/sc_ope/package/1.0.0", "/scope/pack~age/1.0.0", "/scope/package/1_0.0"} { + req := NewRequestWithBody(t, "PUT", url+triple, bytes.NewReader([]byte{})). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusBadRequest) - req := NewRequestWithBody(t, "PUT", url, &body). - SetHeader("Content-Type", mpw.FormDataContentType()). - SetHeader("Accept", swift_router.AcceptJSON). - AddBasicAuth(user.Name) - MakeRequest(t, req, expectedStatus) - } + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) + } + } - for _, triple := range []string{"/sc_ope/package/1.0.0", "/scope/pack~age/1.0.0", "/scope/package/1_0.0"} { - req := NewRequestWithBody(t, "PUT", url+triple, bytes.NewReader([]byte{})). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusBadRequest) + uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, upload.version) - assert.Equal(t, "1", resp.Header().Get("Content-Version")) - assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) - } + req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) + MakeRequest(t, req, http.StatusUnauthorized) - uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion) + // Test with metadata as form field + uploadPackage( + t, + uploadURL, + http.StatusCreated, + test.WriteZipArchive(map[string]string{ + "Package.swift": contentManifest1, + "Package@swift-5.6.swift": contentManifest2, + }), + makePackageMetadataJSON(upload.version), + ) - req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) - MakeRequest(t, req, http.StatusUnauthorized) + if upload.formField { + stableURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2) + uploadPackage(t, stableURL, http.StatusCreated, test.WriteZipArchive(map[string]string{"Package.swift": contentManifest1}), "") + } - uploadPackage( - t, - uploadURL, - http.StatusCreated, - test.WriteZipArchive(map[string]string{ - "Package.swift": contentManifest1, - "Package@swift-5.6.swift": contentManifest2, - }), - makePackageMetadataJSON(packageVersion), - ) - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeSwift) - assert.NoError(t, err) - assert.Len(t, pvs, 1) - - pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) - assert.Equal(t, packageID, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) - metadata, ok := pd.Metadata.(*swift_module.Metadata) - require.True(t, ok) - assert.Equal(t, packageDescription, metadata.Description) - assert.Len(t, metadata.Manifests, 2) - assert.Equal(t, contentManifest1, metadata.Manifests[""].Content) - assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content) - assert.Len(t, pd.VersionProperties, 3) - assert.Equal(t, packageRepositoryURL1, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL)) - - pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 1) - assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion), pfs[0].Name) - assert.True(t, pfs[0].IsLead) - - uploadPackage( - t, - uploadURL, - http.StatusConflict, - test.WriteZipArchive(map[string]string{ - "Package.swift": contentManifest1, - }), - "", - ) - }) - - t.Run("UploadMultipart", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) { - var body bytes.Buffer - mpw := multipart.NewWriter(&body) - - // Read the source archive content - sourceContent, err := io.ReadAll(sr) + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeSwift) assert.NoError(t, err) - mpw.WriteField("source-archive", string(sourceContent)) - - if metadata != "" { - mpw.WriteField("metadata", metadata) + versionIndex := 0 + if upload.formField { + require.Len(t, pvs, 3) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests + versionIndex = 1 + } else { + assert.Len(t, pvs, 1) } - mpw.Close() + pd, err := packages.GetPackageDescriptor(t.Context(), pvs[versionIndex]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.Equal(t, packageID, pd.Package.Name) + assert.Equal(t, upload.version, pd.Version.Version) + metadata, ok := pd.Metadata.(*swift_module.Metadata) + require.True(t, ok) + assert.Equal(t, packageDescription, metadata.Description) + assert.Len(t, metadata.Manifests, 2) + assert.Equal(t, contentManifest1, metadata.Manifests[""].Content) + assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content) + assert.Len(t, pd.VersionProperties, 3) + assert.Equal(t, packageRepositoryURL1, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL)) - req := NewRequestWithBody(t, "PUT", url, &body). - SetHeader("Content-Type", mpw.FormDataContentType()). - SetHeader("Accept", swift_router.AcceptJSON). - AddBasicAuth(user.Name) - MakeRequest(t, req, expectedStatus) - } + pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[versionIndex].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, upload.version), pfs[0].Name) + assert.True(t, pfs[0].IsLead) - uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2Beta) + uploadPackage( + t, + uploadURL, + http.StatusConflict, + test.WriteZipArchive(map[string]string{ + "Package.swift": contentManifest1, + }), + "", + ) - req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) - MakeRequest(t, req, http.StatusUnauthorized) - - // Test with metadata as form field - uploadPackage( - t, - uploadURL, - http.StatusCreated, - test.WriteZipArchive(map[string]string{ - "Package.swift": contentManifest1, - "Package@swift-5.6.swift": contentManifest2, - }), - makePackageMetadataJSON(packageVersion2Beta), - ) - - stableURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2) - uploadPackage(t, stableURL, http.StatusCreated, test.WriteZipArchive(map[string]string{"Package.swift": contentManifest1}), "") - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeSwift) - assert.NoError(t, err) - require.Len(t, pvs, 3) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests - thisPackageVersion := pvs[1] - pd, err := packages.GetPackageDescriptor(t.Context(), thisPackageVersion) - assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) - assert.Equal(t, packageID, pd.Package.Name) - assert.Equal(t, packageVersion2Beta, pd.Version.Version) - metadata, ok := pd.Metadata.(*swift_module.Metadata) - require.True(t, ok) - assert.Equal(t, packageDescription, metadata.Description) - assert.Len(t, metadata.Manifests, 2) - assert.Equal(t, contentManifest1, metadata.Manifests[""].Content) - assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content) - assert.Len(t, pd.VersionProperties, 3) - assert.Equal(t, packageRepositoryURL1, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL)) - - pfs, err := packages.GetFilesByVersionID(t.Context(), thisPackageVersion.ID) - assert.NoError(t, err) - assert.Len(t, pfs, 1) - assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion2Beta), pfs[0].Name) - assert.True(t, pfs[0].IsLead) - - uploadPackage( - t, - uploadURL, - http.StatusConflict, - test.WriteZipArchive(map[string]string{ - "Package.swift": contentManifest1, - }), - "", - ) - - invalidURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, "1.0.6-") - uploadPackage(t, invalidURL, http.StatusBadRequest, bytes.NewReader(nil), "") - }) + if upload.formField { + invalidURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, "1.0.6-") + uploadPackage(t, invalidURL, http.StatusBadRequest, bytes.NewReader(nil), "") + } + }) + } t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 5585ff0cb5a..3008fc67668 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -110,49 +110,40 @@ func TestPackageAPI(t *testing.T) { assert.Equal(t, user.Name, p.Creator.UserName) }) - t.Run("DeleteEntirePackage", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, c := range []struct { + name string + packageName string + suffix string + }{ + {"DeleteEntirePackage", "test-package-entire-delete", ""}, + {"DeletePackageVersion", "test-package-version-delete", "/1.0.1"}, + } { + t.Run(c.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - packageName := "test-package-entire-delete" - for _, version := range []string{"1.0.1", "1.0.2"} { - url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, packageName, version) - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - } + for _, version := range []string{"1.0.1", "1.0.2"} { + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, c.packageName, version) + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + } - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)). - AddTokenAuth(tokenWritePackage) - MakeRequest(t, req, http.StatusNoContent) + url := fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, c.packageName) + c.suffix + req := NewRequest(t, "DELETE", url). + AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)). - AddTokenAuth(tokenReadPackage) - MakeRequest(t, req, http.StatusNotFound) - }) + req = NewRequest(t, "GET", url). + AddTokenAuth(tokenReadPackage) + MakeRequest(t, req, http.StatusNotFound) - t.Run("DeletePackageVersion", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - packageName := "test-package-version-delete" - for _, version := range []string{"1.0.1", "1.0.2"} { - url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, packageName, version) - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - } - - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.1", user.Name, packageName)). - AddTokenAuth(tokenWritePackage) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.1", user.Name, packageName)). - AddTokenAuth(tokenReadPackage) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.2", user.Name, packageName)). - AddTokenAuth(tokenReadPackage) - MakeRequest(t, req, http.StatusOK) - }) + if c.suffix != "" { + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.2", user.Name, c.packageName)). + AddTokenAuth(tokenReadPackage) + MakeRequest(t, req, http.StatusOK) + } + }) + } t.Run("ListPackageVersions", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -317,6 +308,20 @@ func TestPackageAccess(t *testing.T) { ExpectedStatus int } + unauthorizedTargets := []Target{ + {admin, http.StatusUnauthorized}, + {inactive, http.StatusUnauthorized}, + {user, http.StatusUnauthorized}, + {limitedUser, http.StatusUnauthorized}, + {privateUser, http.StatusUnauthorized}, + {privateOrgMember, http.StatusUnauthorized}, + {limitedOrgMember, http.StatusUnauthorized}, + {publicOrgMember, http.StatusUnauthorized}, + {privateOrgNoMember, http.StatusUnauthorized}, + {limitedOrgNoMember, http.StatusUnauthorized}, + {publicOrgNoMember, http.StatusUnauthorized}, + } + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -345,36 +350,12 @@ func TestPackageAccess(t *testing.T) { { // Without credentials no upload should be possible Doer: nil, Filename: "nil", - Targets: []Target{ - {admin, http.StatusUnauthorized}, - {inactive, http.StatusUnauthorized}, - {user, http.StatusUnauthorized}, - {limitedUser, http.StatusUnauthorized}, - {privateUser, http.StatusUnauthorized}, - {privateOrgMember, http.StatusUnauthorized}, - {limitedOrgMember, http.StatusUnauthorized}, - {publicOrgMember, http.StatusUnauthorized}, - {privateOrgNoMember, http.StatusUnauthorized}, - {limitedOrgNoMember, http.StatusUnauthorized}, - {publicOrgNoMember, http.StatusUnauthorized}, - }, + Targets: unauthorizedTargets, }, { // Inactive users can't upload anywhere Doer: inactive, Filename: "inactive", - Targets: []Target{ - {admin, http.StatusUnauthorized}, - {inactive, http.StatusUnauthorized}, - {user, http.StatusUnauthorized}, - {limitedUser, http.StatusUnauthorized}, - {privateUser, http.StatusUnauthorized}, - {privateOrgMember, http.StatusUnauthorized}, - {limitedOrgMember, http.StatusUnauthorized}, - {publicOrgMember, http.StatusUnauthorized}, - {privateOrgNoMember, http.StatusUnauthorized}, - {limitedOrgNoMember, http.StatusUnauthorized}, - {publicOrgNoMember, http.StatusUnauthorized}, - }, + Targets: unauthorizedTargets, }, { // Normal users can upload to self and orgs in which they are members and have package write access Doer: user, @@ -445,20 +426,8 @@ func TestPackageAccess(t *testing.T) { }, }, { // Inactive users have no access - Doer: inactive, - Targets: []Target{ - {admin, http.StatusUnauthorized}, - {inactive, http.StatusUnauthorized}, - {user, http.StatusUnauthorized}, - {limitedUser, http.StatusUnauthorized}, - {privateUser, http.StatusUnauthorized}, - {privateOrgMember, http.StatusUnauthorized}, - {limitedOrgMember, http.StatusUnauthorized}, - {publicOrgMember, http.StatusUnauthorized}, - {privateOrgNoMember, http.StatusUnauthorized}, - {limitedOrgNoMember, http.StatusUnauthorized}, - {publicOrgNoMember, http.StatusUnauthorized}, - }, + Doer: inactive, + Targets: unauthorizedTargets, }, { // Normal users can access self, public or limited users/orgs and private orgs in which they are members Doer: user, diff --git a/tests/integration/api_private_serv_test.go b/tests/integration/api_private_serv_test.go index 30abf963598..296bc9b011c 100644 --- a/tests/integration/api_private_serv_test.go +++ b/tests/integration/api_private_serv_test.go @@ -44,44 +44,38 @@ func TestAPIPrivateServ(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) defer cancel() - // Can push to a repo we own - results, extra := private.ServCommand(ctx, 1, "user2", "repo1", perm.AccessModeWrite, "git-upload-pack", "") - assert.NoError(t, extra.Error) - assert.False(t, results.IsWiki) - assert.Empty(t, results.UserExtDoerData) - assert.Equal(t, int64(1), results.PublicKeyID) - assert.Equal(t, "user2", results.UserName) - assert.Equal(t, int64(2), results.UserID) - assert.Equal(t, "user2", results.OwnerName) - assert.Equal(t, "repo1", results.RepoName) - assert.Equal(t, int64(1), results.RepoID) - - // Cannot push to a private repo we're not associated with - results, extra = private.ServCommand(ctx, 1, "user15", "big_test_private_1", perm.AccessModeWrite, "git-upload-pack", "") - assert.Error(t, extra.Error) - assert.Empty(t, results) - - // Cannot pull from a private repo we're not associated with - results, extra = private.ServCommand(ctx, 1, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "") - assert.Error(t, extra.Error) - assert.Empty(t, results) - - // Can pull from a public repo we're not associated with - results, extra = private.ServCommand(ctx, 1, "user15", "big_test_public_1", perm.AccessModeRead, "git-upload-pack", "") - assert.NoError(t, extra.Error) - assert.False(t, results.IsWiki) - assert.Empty(t, results.UserExtDoerData) - assert.Equal(t, int64(1), results.PublicKeyID) - assert.Equal(t, "user2", results.UserName) - assert.Equal(t, int64(2), results.UserID) - assert.Equal(t, "user15", results.OwnerName) - assert.Equal(t, "big_test_public_1", results.RepoName) - assert.Equal(t, int64(17), results.RepoID) - - // Cannot push to a public repo we're not associated with - results, extra = private.ServCommand(ctx, 1, "user15", "big_test_public_1", perm.AccessModeWrite, "git-upload-pack", "") - assert.Error(t, extra.Error) - assert.Empty(t, results) + for _, testCase := range []struct { + ownerName, repoName string + mode perm.AccessMode + repoID int64 + }{ + // Can push to a repo we own + {"user2", "repo1", perm.AccessModeWrite, 1}, + // Cannot push to a private repo we're not associated with + {"user15", "big_test_private_1", perm.AccessModeWrite, 0}, + // Cannot pull from a private repo we're not associated with + {"user15", "big_test_private_1", perm.AccessModeRead, 0}, + // Can pull from a public repo we're not associated with + {"user15", "big_test_public_1", perm.AccessModeRead, 17}, + // Cannot push to a public repo we're not associated with + {"user15", "big_test_public_1", perm.AccessModeWrite, 0}, + } { + results, extra := private.ServCommand(ctx, 1, testCase.ownerName, testCase.repoName, testCase.mode, "git-upload-pack", "") + if testCase.repoID == 0 { + assert.Error(t, extra.Error) + assert.Empty(t, results) + } else { + assert.NoError(t, extra.Error) + assert.False(t, results.IsWiki) + assert.Empty(t, results.UserExtDoerData) + assert.Equal(t, int64(1), results.PublicKeyID) + assert.Equal(t, "user2", results.UserName) + assert.Equal(t, int64(2), results.UserID) + assert.Equal(t, testCase.ownerName, results.OwnerName) + assert.Equal(t, testCase.repoName, results.RepoName) + assert.Equal(t, testCase.repoID, results.RepoID) + } + } // Add reading deploy key testContent := "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment" @@ -90,7 +84,7 @@ func TestAPIPrivateServ(t *testing.T) { // Can pull from repo we're a deploy-key for deployKeyUser := user.NewDeployKeyUser() - results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "") + results, extra := private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "") assert.NoError(t, extra.Error) assert.False(t, results.IsWiki) assert.NotEmpty(t, results.UserExtDoerData) @@ -126,28 +120,22 @@ func TestAPIPrivateServ(t *testing.T) { assert.Error(t, extra.Error) assert.Empty(t, results) - // Can pull from repo we're a writing deploy key for - results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeRead, "git-upload-pack", "") - assert.NoError(t, extra.Error) - assert.False(t, results.IsWiki) - assert.NotEmpty(t, results.UserExtDoerData) - assert.Equal(t, deployKey.KeyID, results.PublicKeyID) - assert.Equal(t, deployKeyUser.Name, results.UserName) - assert.Equal(t, deployKeyUser.ID, results.UserID) - assert.Equal(t, "user15", results.OwnerName) - assert.Equal(t, "big_test_private_2", results.RepoName) - assert.Equal(t, int64(20), results.RepoID) - - // Can push to repo we're a writing deploy key for - results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeWrite, "git-upload-pack", "") - assert.NoError(t, extra.Error) - assert.False(t, results.IsWiki) - assert.NotEmpty(t, results.UserExtDoerData) - assert.Equal(t, deployKey.KeyID, results.PublicKeyID) - assert.Equal(t, deployKeyUser.Name, results.UserName) - assert.Equal(t, deployKeyUser.ID, results.UserID) - assert.Equal(t, "user15", results.OwnerName) - assert.Equal(t, "big_test_private_2", results.RepoName) - assert.Equal(t, int64(20), results.RepoID) + for _, mode := range []perm.AccessMode{ + // Can pull from repo we're a writing deploy key for + perm.AccessModeRead, + // Can push to repo we're a writing deploy key for + perm.AccessModeWrite, + } { + results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", mode, "git-upload-pack", "") + assert.NoError(t, extra.Error) + assert.False(t, results.IsWiki) + assert.NotEmpty(t, results.UserExtDoerData) + assert.Equal(t, deployKey.KeyID, results.PublicKeyID) + assert.Equal(t, deployKeyUser.Name, results.UserName) + assert.Equal(t, deployKeyUser.ID, results.UserID) + assert.Equal(t, "user15", results.OwnerName) + assert.Equal(t, "big_test_private_2", results.RepoName) + assert.Equal(t, int64(20), results.RepoID) + } }) } diff --git a/tests/integration/api_public_only_test.go b/tests/integration/api_public_only_test.go index 3a24d2b4d20..16a79d696ce 100644 --- a/tests/integration/api_public_only_test.go +++ b/tests/integration/api_public_only_test.go @@ -23,28 +23,20 @@ func TestAPIUserReposPublicOnly(t *testing.T) { defer tests.PrepareTestEnv(t)() token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/user/repos"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var repos []api.Repository - DecodeJSON(t, resp, &repos) - assert.NotEmpty(t, repos) - for _, repo := range repos { - assert.False(t, repo.Private) + for index, url := range []string{"/api/v1/user/repos", "/api/v1/users/user2/repos"} { + req := NewRequest(t, "GET", url).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repos) + assert.NotEmpty(t, repos) + for _, repo := range repos { + assert.False(t, repo.Private) + } + assert.NotContains(t, repoNames(repos), "user2/repo2") + if index == 1 { + assert.Equal(t, strconv.Itoa(len(repos)), resp.Header().Get("X-Total-Count")) + } } - assert.NotContains(t, repoNames(repos), "user2/repo2") - - req = NewRequest(t, "GET", "/api/v1/users/user2/repos"). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &repos) - assert.NotEmpty(t, repos) - for _, repo := range repos { - assert.False(t, repo.Private) - } - assert.NotContains(t, repoNames(repos), "user2/repo2") - assert.Equal(t, strconv.Itoa(len(repos)), resp.Header().Get("X-Total-Count")) } func repoNames(repos []api.Repository) []string { @@ -55,77 +47,68 @@ func repoNames(repos []api.Repository) []string { return names } -func TestAPIRepoByIDPublicOnly(t *testing.T) { +func TestAPIPublicOnlyReadEndpoints(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/repositories/1"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + t.Run("RepoByID", func(t *testing.T) { + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) + req := NewRequest(t, "GET", "/api/v1/repositories/1"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", "/api/v1/repositories/2"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} + req = NewRequest(t, "GET", "/api/v1/repositories/2"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) -func TestAPIActivityFeedsPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("ActivityFeeds", func(t *testing.T) { + var activities []api.Activity + for _, testCase := range []struct { + url string + scope auth_model.AccessTokenScope + tokenOwners []string + }{ + {"/api/v1/users/user2/activities/feeds", auth_model.AccessTokenScopeReadUser, []string{"user2", "user1"}}, + {"/api/v1/orgs/org3/activities/feeds", auth_model.AccessTokenScopeReadOrganization, []string{"user2"}}, + } { + token := getUserToken(t, "user2", testCase.scope) + req := NewRequest(t, "GET", testCase.url).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &activities) + assert.NotEmpty(t, activities) - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser) - req := NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var activities []api.Activity - DecodeJSON(t, resp, &activities) - assert.NotEmpty(t, activities) + for _, tokenOwner := range testCase.tokenOwners { + publicToken := getUserToken(t, tokenOwner, testCase.scope, auth_model.AccessTokenScopePublicOnly) + req = NewRequest(t, "GET", testCase.url).AddTokenAuth(publicToken) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &activities) + assertPublicActivitiesOnly(t, activities) + } + } + }) - for _, tokenOwner := range []string{"user2", "user1"} { - publicToken := getUserToken(t, tokenOwner, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) - req = NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds"). - AddTokenAuth(publicToken) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &activities) - assertPublicActivitiesOnly(t, activities) - } + t.Run("OrgMembership", func(t *testing.T) { + // user2 is a member of the private org private_org35 + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) - orgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) - req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds"). - AddTokenAuth(orgToken) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &activities) - assert.NotEmpty(t, activities) + // a full org-scoped token can read the membership permissions + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization) + req := NewRequestf(t, "GET", "/api/v1/users/user2/orgs/%s/permissions", org.Name).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) - publicOrgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly) - req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds"). - AddTokenAuth(publicOrgToken) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &activities) - assertPublicActivitiesOnly(t, activities) -} + // a public-only token must not disclose permissions for a private org + publicToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly) + req = NewRequestf(t, "GET", "/api/v1/users/user2/orgs/%s/permissions", org.Name).AddTokenAuth(publicToken) + MakeRequest(t, req, http.StatusNotFound) -func TestAPIOrgMembershipPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user2 is a member of the private org private_org35 - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) - - // a full org-scoped token can read the membership permissions - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization) - req := NewRequestf(t, "GET", "/api/v1/users/user2/orgs/%s/permissions", org.Name).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - - // a public-only token must not disclose permissions for a private org - publicToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly) - req = NewRequestf(t, "GET", "/api/v1/users/user2/orgs/%s/permissions", org.Name).AddTokenAuth(publicToken) - MakeRequest(t, req, http.StatusNotFound) - - var members, publicMembers []*api.User - DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members").AddTokenAuth(token), http.StatusOK), &members) - DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members").AddTokenAuth(publicToken), http.StatusOK), &publicMembers) - assert.Len(t, members, 3) - assert.Len(t, publicMembers, 2) - MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members/user4").AddTokenAuth(token), http.StatusNoContent) - MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members/user4").AddTokenAuth(publicToken), http.StatusSeeOther) + var members, publicMembers []*api.User + DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members").AddTokenAuth(token), http.StatusOK), &members) + DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members").AddTokenAuth(publicToken), http.StatusOK), &publicMembers) + assert.Len(t, members, 3) + assert.Len(t, publicMembers, 2) + MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members/user4").AddTokenAuth(token), http.StatusNoContent) + MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members/user4").AddTokenAuth(publicToken), http.StatusSeeOther) + }) } func TestAPITeamReposPublicOnly(t *testing.T) { diff --git a/tests/integration/api_pull_commits_test.go b/tests/integration/api_pull_commits_test.go deleted file mode 100644 index 3c326046228..00000000000 --- a/tests/integration/api_pull_commits_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - issues_model "gitea.dev/models/issues" - repo_model "gitea.dev/models/repo" - "gitea.dev/models/unittest" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAPIPullCommits(t *testing.T) { - defer tests.PrepareTestEnv(t)() - pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) - assert.NoError(t, pr.LoadIssue(t.Context())) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pr.HeadRepoID}) - - req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/commits", repo.OwnerName, repo.Name, pr.Index) - resp := MakeRequest(t, req, http.StatusOK) - - commits := DecodeJSON(t, resp, []*api.Commit{}) - - require.Len(t, commits, 2) - - assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", commits[0].SHA) - assert.Equal(t, "5c050d3b6d2db231ab1f64e324f1b6b9a0b181c2", commits[1].SHA) - - assert.NotEmpty(t, commits[0].Files) - assert.NotEmpty(t, commits[1].Files) - assert.NotNil(t, commits[0].RepoCommit.Verification) - assert.NotNil(t, commits[1].RepoCommit.Verification) -} - -// TODO add tests for already merged PR and closed PR diff --git a/tests/integration/api_pull_review_test.go b/tests/integration/api_pull_review_test.go index fa82c909565..6ab2fb4546e 100644 --- a/tests/integration/api_pull_review_test.go +++ b/tests/integration/api_pull_review_test.go @@ -30,6 +30,28 @@ import ( func TestAPIPullReview(t *testing.T) { defer tests.PrepareTestEnv(t)() + + // TODO add tests for already merged PR and closed PR + t.Run("Commits", func(t *testing.T) { + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + assert.NoError(t, pr.LoadIssue(t.Context())) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pr.HeadRepoID}) + + req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/commits", repo.OwnerName, repo.Name, pr.Index) + resp := MakeRequest(t, req, http.StatusOK) + + commits := DecodeJSON(t, resp, []*api.Commit{}) + + require.Len(t, commits, 2) + + assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", commits[0].SHA) + assert.Equal(t, "5c050d3b6d2db231ab1f64e324f1b6b9a0b181c2", commits[1].SHA) + + assert.NotEmpty(t, commits[0].Files) + assert.NotEmpty(t, commits[1].Files) + assert.NotNil(t, commits[0].RepoCommit.Verification) + assert.NotNil(t, commits[1].RepoCommit.Verification) + }) t.Run("General", testAPIPullReviewGeneral) t.Run("CommentReply", testAPIPullReviewCommentReply) } @@ -273,46 +295,44 @@ func TestAPIPullReviewRequest(t *testing.T) { pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) assert.NoError(t, pullIssue.LoadAttributes(t.Context())) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID}) + requestReview := func(method string, repo *repo_model.Repository, index int64, token string, options *api.PullReviewRequestOptions, expectedStatus int) { + req := NewRequestWithJSON(t, method, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, index), options).AddTokenAuth(token) + MakeRequest(t, req, expectedStatus) + } // Test add Review Request session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo, pullIssue.Index, token, &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com", "user8"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) // poster of pr can't be reviewer - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo, pullIssue.Index, token, &api.PullReviewRequestOptions{ Reviewers: []string{"user1"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + }, http.StatusUnprocessableEntity) // test user not exist - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo, pullIssue.Index, token, &api.PullReviewRequestOptions{ Reviewers: []string{"testOther"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + }, http.StatusNotFound) // Test Remove Review Request session2 := loginUser(t, "user4") token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, repo, pullIssue.Index, token2, &api.PullReviewRequestOptions{ Reviewers: []string{"user4"}, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // doer is not admin - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, repo, pullIssue.Index, token2, &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusUnprocessableEntity) + }, http.StatusUnprocessableEntity) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, repo, pullIssue.Index, token, &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // a collaborator can add/remove a review request pullIssue21 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 21}) @@ -320,42 +340,36 @@ func TestAPIPullReviewRequest(t *testing.T) { pull21Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue21.RepoID}) // repo60 user38Session := loginUser(t, "user38") user38Token := getTokenForLoggedInUser(t, user38Session, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, pull21Repo, pullIssue21.Index, user38Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com"}, - }).AddTokenAuth(user38Token) - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, pull21Repo, pullIssue21.Index, user38Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com"}, - }).AddTokenAuth(user38Token) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // the poster of the PR can add/remove a review request user39Session := loginUser(t, "user39") user39Token := getTokenForLoggedInUser(t, user39Session, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, pull21Repo, pullIssue21.Index, user39Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, - }).AddTokenAuth(user39Token) - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, pull21Repo, pullIssue21.Index, user39Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, - }).AddTokenAuth(user39Token) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // user with read permission on pull requests unit can add/remove a review request pullIssue22 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 22}) assert.NoError(t, pullIssue22.LoadAttributes(t.Context())) pull22Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue22.RepoID}) // repo61 - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, pull22Repo, pullIssue22.Index, user39Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user38"}, - }).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) // user39 is from a team with read permission on pull requests unit - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, pull22Repo, pullIssue22.Index, user39Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user38"}, - }).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // user39 is from a team with read permission on pull requests unit // Test team review request pullIssue12 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 12}) @@ -363,37 +377,29 @@ func TestAPIPullReviewRequest(t *testing.T) { repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID}) // Test add Team Review Request - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{ TeamReviewers: []string{"team1", "owners"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) // Test add Team Review Request to not allowned - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{ TeamReviewers: []string{"test_team"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + }, http.StatusUnprocessableEntity) // Test add Team Review Request to not exist - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{ TeamReviewers: []string{"not_exist_team"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + }, http.StatusNotFound) // Test Remove team Review Request - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{ TeamReviewers: []string{"team1"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // empty request test - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + requestReview(http.MethodPost, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{}, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + requestReview(http.MethodDelete, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{}, http.StatusNoContent) } func TestAPIPullReviewCommentResolveEndpoints(t *testing.T) { @@ -484,27 +490,20 @@ func TestAPIPullReviewStayDismissed(t *testing.T) { token8 := getTokenForLoggedInUser(t, session8, auth_model.AccessTokenScopeWriteRepository) // user2 request user8 - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ - Reviewers: []string{user8.LoginName}, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - reviewsCountCheck(t, + for _, name := range []string{ "check we have only one review request", - pullIssue.ID, user8.ID, 0, 1, 1, false) - - // user2 request user8 again, it is expected to be ignored - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ - Reviewers: []string{user8.LoginName}, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - reviewsCountCheck(t, + // user2 request user8 again, it is expected to be ignored "check we have only one review request, even after re-request it again", - pullIssue.ID, user8.ID, 0, 1, 1, false) + } { + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + Reviewers: []string{user8.LoginName}, + }).AddTokenAuth(token2) + MakeRequest(t, req, http.StatusCreated) + reviewsCountCheck(t, name, pullIssue.ID, user8.ID, 0, 1, 1, false) + } // user8 reviews it as accept - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Event: "APPROVED", Body: "lgtm", }).AddTokenAuth(token8) diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index b2464d10a20..d7ddced4ba7 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -144,6 +144,30 @@ func TestAPIViewPulls(t *testing.T) { })) } + t.Run("APIViewPullsByBaseHead", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name). + AddTokenAuth(ctx.Token) + resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + + pull := DecodeJSON(t, resp, &api.PullRequest{}) + assert.EqualValues(t, 3, pull.Index) + assert.EqualValues(t, 2, pull.ID) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch-not-exist", owner.Name, repo.Name). + AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("APICommitPullRequest", func(t *testing.T) { + mergedCommitSHA := "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3" + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, req, http.StatusOK) + + invalidCommitSHA := "abcd1234abcd1234abcd1234abcd1234abcd1234" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, req, http.StatusNotFound) + }) + t.Run("HeadRepoHiddenWhenInaccessible", func(t *testing.T) { session := loginUser(t, "user31") forkToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) @@ -173,26 +197,6 @@ func TestAPIViewPulls(t *testing.T) { }) } -func TestAPIViewPullsByBaseHead(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name). - AddTokenAuth(ctx.Token) - resp := ctx.Session.MakeRequest(t, req, http.StatusOK) - - pull := DecodeJSON(t, resp, &api.PullRequest{}) - assert.EqualValues(t, 3, pull.Index) - assert.EqualValues(t, 2, pull.ID) - - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch-not-exist", owner.Name, repo.Name). - AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, req, http.StatusNotFound) -} - // TestAPIMergePullWIP ensures that we can't merge a WIP pull request func TestAPIMergePullWIP(t *testing.T) { defer tests.PrepareTestEnv(t)() @@ -611,22 +615,6 @@ func doAPIGetPullFiles(ctx APITestContext, pr *api.PullRequest, callback func(*t } } -func TestAPICommitPullRequest(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) - - mergedCommitSHA := "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3" - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, req, http.StatusOK) - - invalidCommitSHA := "abcd1234abcd1234abcd1234abcd1234abcd1234" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, req, http.StatusNotFound) -} - func TestAPIViewPullFilesWithHeadRepoDeleted(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index fb6f04ada24..51f909f1746 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -33,6 +33,30 @@ import ( func TestAPIReleaseRead(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("CreateProtectedTagRelease", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + writer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + session := loginUser(t, writer.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + + commit, err := gitRepo.GetBranchCommit(t.Context(), "master") + assert.NoError(t, err) + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/releases", repo.OwnerName, repo.Name), &api.CreateReleaseOption{ + TagName: "v0.0.1", + Title: "v0.0.1", + IsDraft: false, + IsPrerelease: false, + Target: commit.ID.String(), + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + }) t.Run("DraftReleaseAttachmentAccess", testAPIDraftReleaseAttachmentAccess) t.Run("ListReleasesWithWriteToken", testAPIListReleasesWithWriteToken) t.Run("ListReleasesWithReadToken", testAPIListReleasesWithReadToken) @@ -225,76 +249,43 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { assert.Equal(t, rel.Note, newRelease.Note) } -func TestAPICreateProtectedTagRelease(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func TestAPICreateRelease(t *testing.T) { + for _, scenario := range []string{"InvalidTarget", "DefaultBranch", "ExistingTag"} { + t.Run(scenario, func(t *testing.T) { + if scenario == "DefaultBranch" { + defer tests.PrintCurrentTest(t)() + } else { + defer tests.PrepareTestEnv(t)() + } - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - writer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - session := loginUser(t, writer.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() + if scenario == "ExistingTag" { + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() - commit, err := gitRepo.GetBranchCommit(t.Context(), "master") - assert.NoError(t, err) + err = gitRepo.CreateTag(t.Context(), "v0.0.1", "master") + assert.NoError(t, err) + } - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/releases", repo.OwnerName, repo.Name), &api.CreateReleaseOption{ - TagName: "v0.0.1", - Title: "v0.0.1", - IsDraft: false, - IsPrerelease: false, - Target: commit.ID.String(), - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) -} + if scenario == "InvalidTarget" { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{ + TagName: "i-point-to-an-invalid-target", + Title: "Invalid Target", + Target: "invalid-target", + }).AddTokenAuth(token) -func TestAPICreateReleaseToDefaultBranch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") -} - -func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - - err = gitRepo.CreateTag(t.Context(), "v0.0.1", "master") - assert.NoError(t, err) - - createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") -} - -func TestAPICreateReleaseGivenInvalidTarget(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name) - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{ - TagName: "i-point-to-an-invalid-target", - Title: "Invalid Target", - Target: "invalid-target", - }).AddTokenAuth(token) - - MakeRequest(t, req, http.StatusNotFound) + MakeRequest(t, req, http.StatusNotFound) + } else { + createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") + } + }) + } } func testAPIGetLatestRelease(t *testing.T) { diff --git a/tests/integration/api_repo_avatar_test.go b/tests/integration/api_repo_avatar_test.go index 431e1bee20e..8c43629a9d6 100644 --- a/tests/integration/api_repo_avatar_test.go +++ b/tests/integration/api_repo_avatar_test.go @@ -29,48 +29,33 @@ func TestAPIUpdateRepoAvatar(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository) - // Test what happens if you use a valid image + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) + avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) assert.NoError(t, err) if err != nil { assert.FailNow(t, "Unable to open avatar.png") } - opts := api.UpdateRepoAvatarOption{ - Image: base64.StdEncoding.EncodeToString(avatar), + for _, avatarCase := range []struct { + image string + status int + }{ + // Test what happens if you use a valid image + {base64.StdEncoding.EncodeToString(avatar), http.StatusNoContent}, + // Test what happens if you don't have a valid Base64 string + {"Invalid", http.StatusBadRequest}, + // Test what happens if you use a file that is not an image + {base64.StdEncoding.EncodeToString([]byte("This is not an image")), http.StatusInternalServerError}, + } { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &api.UpdateRepoAvatarOption{Image: avatarCase.image}). + AddTokenAuth(token) + MakeRequest(t, req, avatarCase.status) } - - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Test what happens if you don't have a valid Base64 string - opts = api.UpdateRepoAvatarOption{ - Image: "Invalid", - } - - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) - - // Test what happens if you use a file that is not an image - opts = api.UpdateRepoAvatarOption{ - Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")), - } - - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusInternalServerError) -} - -func TestAPIDeleteRepoAvatar(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository) - - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) } diff --git a/tests/integration/api_repo_branch_test.go b/tests/integration/api_repo_branch_test.go index 864e351c8d0..2980647c782 100644 --- a/tests/integration/api_repo_branch_test.go +++ b/tests/integration/api_repo_branch_test.go @@ -18,130 +18,127 @@ import ( "gitea.dev/modules/json" "gitea.dev/modules/setting" api "gitea.dev/modules/structs" - "gitea.dev/tests" "github.com/stretchr/testify/assert" ) -func TestAPIRepoBranchesPlain(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - session := loginUser(t, user1.LowerName) +func TestAPIRepoBranches(t *testing.T) { + onGiteaRun(t, func(t *testing.T, _url *url.URL) { + t.Run("Plain", func(t *testing.T) { + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user1.LowerName) - // public-only token cannot see a private repo - publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository) - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo - MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + // public-only token cannot see a private repo + publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo + MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) - bs, err := io.ReadAll(resp.Body) - assert.NoError(t, err) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) + bs, err := io.ReadAll(resp.Body) + assert.NoError(t, err) - var branches []*api.Branch - assert.NoError(t, json.Unmarshal(bs, &branches)) - assert.Len(t, branches, 2) - assert.Equal(t, "test_branch", branches[0].Name) - assert.Equal(t, "master", branches[1].Name) + var branches []*api.Branch + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 2) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "master", branches[1].Name) - link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name)) - MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name)) + MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) - resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - var branch api.Branch - assert.NoError(t, json.Unmarshal(bs, &branch)) - assert.Equal(t, "test_branch", branch.Name) + resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch)) + assert.Equal(t, "test_branch", branch.Name) - MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) - req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) - req.Header.Add("Content-Type", "application/json") - req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) - resp = MakeRequest(t, req, http.StatusCreated) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - var branch2 api.Branch - assert.NoError(t, json.Unmarshal(bs, &branch2)) - assert.Equal(t, "test_branch2", branch2.Name) - assert.Equal(t, branch.Commit.ID, branch2.Commit.ID) + req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) + req.Header.Add("Content-Type", "application/json") + req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + resp = MakeRequest(t, req, http.StatusCreated) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch2 api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch2)) + assert.Equal(t, "test_branch2", branch2.Name) + assert.Equal(t, branch.Commit.ID, branch2.Commit.ID) - resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) + resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) - branches = []*api.Branch{} - assert.NoError(t, json.Unmarshal(bs, &branches)) - assert.Len(t, branches, 3) - assert.Equal(t, "test_branch", branches[0].Name) - assert.Equal(t, "test_branch2", branches[1].Name) - assert.Equal(t, "master", branches[2].Name) + branches = []*api.Branch{} + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 3) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "test_branch2", branches[1].Name) + assert.Equal(t, "master", branches[2].Name) - link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name)) - MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound) - MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name)) + MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) - MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent) - assert.NoError(t, err) + MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent) + assert.NoError(t, err) + }) + + t.Run("Mirror", func(t *testing.T) { + repo5 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user1.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo5.Name)) // a mirror repo + resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) + bs, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + + var branches []*api.Branch + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 2) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "master", branches[1].Name) + + link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo5.Name)) + resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch)) + assert.Equal(t, "test_branch", branch.Name) + + req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) + req.Header.Add("Content-Type", "application/json") + req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + resp = MakeRequest(t, req, http.StatusForbidden) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) + + resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()).AddTokenAuth(token), http.StatusForbidden) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) + }) + + t.Run("Search", func(t *testing.T) { + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository) + + // "test" matches "test_branch" but not "master" + resp := MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=test").AddTokenAuth(token), http.StatusOK) + branches := DecodeJSON(t, resp, []api.Branch{}) + assert.Len(t, branches, 1) + assert.Equal(t, "test_branch", branches[0].Name) + + // no match returns empty list + resp = MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=doesnotexist").AddTokenAuth(token), http.StatusOK) + branches = DecodeJSON(t, resp, []api.Branch{}) + assert.Empty(t, branches) + }) }) } - -func TestAPIRepoBranchesMirror(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo5 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - session := loginUser(t, user1.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo5.Name)) // a mirror repo - resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) - bs, err := io.ReadAll(resp.Body) - assert.NoError(t, err) - - var branches []*api.Branch - assert.NoError(t, json.Unmarshal(bs, &branches)) - assert.Len(t, branches, 2) - assert.Equal(t, "test_branch", branches[0].Name) - assert.Equal(t, "master", branches[1].Name) - - link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo5.Name)) - resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - var branch api.Branch - assert.NoError(t, json.Unmarshal(bs, &branch)) - assert.Equal(t, "test_branch", branch.Name) - - req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) - req.Header.Add("Content-Type", "application/json") - req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) - resp = MakeRequest(t, req, http.StatusForbidden) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) - - resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()).AddTokenAuth(token), http.StatusForbidden) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) -} - -func TestAPIRepoBranchesSearch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository) - - // "test" matches "test_branch" but not "master" - resp := MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=test").AddTokenAuth(token), http.StatusOK) - branches := DecodeJSON(t, resp, []api.Branch{}) - assert.Len(t, branches, 1) - assert.Equal(t, "test_branch", branches[0].Name) - - // no match returns empty list - resp = MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=doesnotexist").AddTokenAuth(token), http.StatusOK) - branches = DecodeJSON(t, resp, []api.Branch{}) - assert.Empty(t, branches) -} diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go index 00afa3ea479..8bd821a8f9a 100644 --- a/tests/integration/api_repo_collaborator_test.go +++ b/tests/integration/api_repo_collaborator_test.go @@ -8,14 +8,17 @@ import ( "testing" auth_model "gitea.dev/models/auth" + "gitea.dev/models/organization" "gitea.dev/models/perm" repo_model "gitea.dev/models/repo" "gitea.dev/models/unittest" user_model "gitea.dev/models/user" api "gitea.dev/modules/structs" + repo_service "gitea.dev/services/repository" "gitea.dev/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAPIRepoCollaboratorPermission(t *testing.T) { @@ -41,41 +44,27 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { assert.Equal(t, api.AccessLevelNameOwner, repoPermission.Permission) }) - t.Run("CollaboratorWithReadAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) + for _, testCase := range []struct { + name string + mode perm.AccessMode + permission api.AccessLevelName + }{ + {"Read", perm.AccessModeRead, api.AccessLevelNameRead}, + {"Write", perm.AccessModeWrite, api.AccessLevelNameWrite}, + {"Admin", perm.AccessModeAdmin, api.AccessLevelNameAdmin}, + } { + t.Run("CollaboratorWith"+testCase.name+"Access", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWith"+testCase.name+"Access", doAPIAddCollaborator(testCtx, user4.Name, testCase.mode)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) + repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) - }) - - t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) - - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - - assert.Equal(t, api.AccessLevelNameWrite, repoPermission.Permission) - }) - - t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) - - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - - assert.Equal(t, api.AccessLevelNameAdmin, repoPermission.Permission) - }) + assert.Equal(t, testCase.permission, repoPermission.Permission) + }) + } t.Run("CollaboratorNotFound", func(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user"). @@ -89,47 +78,36 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t) }) - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + for _, readOwnPermission := range []bool{true, false} { + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) - _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). + AddTokenAuth(_testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) + repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) + assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) - t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) { - session := loginUser(t, user5.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + if readOwnPermission { + t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) { + session := loginUser(t, user5.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) - repoCollPerm := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) + repoCollPerm := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - assert.Equal(t, api.AccessLevelNameRead, repoCollPerm.Permission) + assert.Equal(t, api.AccessLevelNameRead, repoCollPerm.Permission) + }) + } }) - }) - - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) - - _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) - - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - - assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) - }) + } t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) { t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin)) @@ -147,3 +125,36 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) }) } + +func TestAPIRepositoryDelete(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("AdminNoPermToDeleteRepo", func(t *testing.T) { + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) + teams, err := organization.GetUserOrgTeams(t.Context(), org.ID, doer.ID) + require.NoError(t, err) + require.Len(t, teams, 1) + + team := teams[0] + assert.Equal(t, perm.AccessModeAdmin, team.AccessMode) + assert.True(t, team.CanCreateOrgRepo) + token := getUserToken(t, doer.Name, auth_model.AccessTokenScopeWriteRepository) + + unrelatedRepo, err := repo_service.CreateRepository(t.Context(), owner, org, repo_service.CreateRepoOptions{Name: "unrelated-admin-team"}) + require.NoError(t, err) + + targetRepo, err := repo_service.CreateRepository(t.Context(), owner, org, repo_service.CreateRepoOptions{Name: "target-admin-team"}) + require.NoError(t, err) + require.NoError(t, repo_service.TeamAddRepository(t.Context(), team, targetRepo)) + + req := NewRequest(t, "DELETE", "/api/v1/repos/"+unrelatedRepo.FullName()).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: unrelatedRepo.ID}) + + req = NewRequest(t, "DELETE", "/api/v1/repos/"+targetRepo.FullName()).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &repo_model.Repository{ID: targetRepo.ID}) + }) +} diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go index 57f943c5adf..c44f9608b66 100644 --- a/tests/integration/api_repo_compare_test.go +++ b/tests/integration/api_repo_compare_test.go @@ -143,30 +143,23 @@ func TestAPIDownloadCompareDiffOrPatch(t *testing.T) { assert.True(t, strings.HasPrefix(body, "From "), "patch output should start with a format-patch header, got: %q", body[:min(40, len(body))]) }) - t.Run("CommitToCommitDiff", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f?output=diff").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "diff --git ") - }) - - t.Run("BranchToCommitDiff", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + for _, testCase := range []struct { + name string + url string + }{ + {"CommitToCommitDiff", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f?output=diff"}, // 8babce96... is the head of remove-files-b; pairing it with add-csv guarantees a non-empty diff. - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...8babce967f21b9dfa6987f943b91093dac58a4f0?output=diff").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "diff --git ") - }) + {"BranchToCommitDiff", "/api/v1/repos/user2/repo20/compare/add-csv...8babce967f21b9dfa6987f943b91093dac58a4f0?output=diff"}, + {"TwoDotSeparator", "/api/v1/repos/user2/repo20/compare/add-csv..remove-files-b?output=diff"}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("TwoDotSeparator", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv..remove-files-b?output=diff").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "diff --git ") - }) + req := NewRequest(t, "GET", testCase.url).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "diff --git ") + }) + } t.Run("SlashedBranchName", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_repo_delete_test.go b/tests/integration/api_repo_delete_test.go deleted file mode 100644 index ae566c42804..00000000000 --- a/tests/integration/api_repo_delete_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/organization" - "gitea.dev/models/perm" - repo_model "gitea.dev/models/repo" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - repo_service "gitea.dev/services/repository" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAPIRepositoryDelete(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - t.Run("AdminNoPermToDeleteRepo", func(t *testing.T) { - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - teams, err := organization.GetUserOrgTeams(t.Context(), org.ID, doer.ID) - require.NoError(t, err) - require.Len(t, teams, 1) - - team := teams[0] - assert.Equal(t, perm.AccessModeAdmin, team.AccessMode) - assert.True(t, team.CanCreateOrgRepo) - token := getUserToken(t, doer.Name, auth_model.AccessTokenScopeWriteRepository) - - unrelatedRepo, err := repo_service.CreateRepository(t.Context(), owner, org, repo_service.CreateRepoOptions{Name: "unrelated-admin-team"}) - require.NoError(t, err) - - targetRepo, err := repo_service.CreateRepository(t.Context(), owner, org, repo_service.CreateRepoOptions{Name: "target-admin-team"}) - require.NoError(t, err) - require.NoError(t, repo_service.TeamAddRepository(t.Context(), team, targetRepo)) - - req := NewRequest(t, "DELETE", "/api/v1/repos/"+unrelatedRepo.FullName()).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: unrelatedRepo.ID}) - - req = NewRequest(t, "DELETE", "/api/v1/repos/"+targetRepo.FullName()).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &repo_model.Repository{ID: targetRepo.ID}) - }) -} diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index 631a9248a3d..47bf8fa811b 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -136,11 +136,10 @@ func TestAPICreateFile(t *testing.T) { fileID := 0 // Get user2's token - session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + session2 := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) // Get user4's token - session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + token4 := getTokenForLoggedInUser(t, loginUser(t, user4.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) // Test creating a file in repo1 which user2 owns, try both with branch and empty branch for _, branch := range [...]string{ @@ -222,54 +221,42 @@ func TestAPICreateFile(t *testing.T) { apiError := DecodeJSON(t, resp, &context.APIError{}) assert.Equal(t, expectedAPIError, apiError) - // Test creating a file in repo1 by user4 who does not have write access - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "org3/repo3" where user2 is a collaborator - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "org3/repo3" with no user token - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) + for _, testCase := range []struct { + owner *user_model.User + repo *repo_model.Repository + token string + expectedStatus int + }{ + // Test creating a file in repo1 by user4 who does not have write access + {user2, repo16, token4, http.StatusNotFound}, + // Tests a repo with no token given so will fail + {user2, repo16, "", http.StatusNotFound}, + // Test using access token for a private repo that the user of the token owns + {user2, repo16, token2, http.StatusCreated}, + // Test using org repo "org3/repo3" where user2 is a collaborator + {org3, repo3, token2, http.StatusCreated}, + // Test using org repo "org3/repo3" with no user token + {org3, repo3, "", http.StatusNotFound}, + // Test using repo "user2/repo1" where user4 is a NOT collaborator + {user2, repo1, token4, http.StatusForbidden}, + } { + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCase.owner.Name, testCase.repo.Name, treePath), &createFileOptions) + if testCase.token != "" { + req.AddTokenAuth(testCase.token) + } + MakeRequest(t, req, testCase.expectedStatus) + } // Test creating a file in an empty repository - doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true)(t) + doAPICreateRepository(APITestContext{ + Session: session2, + Token: token2, + Username: "user2", + Reponame: "empty-repo", + }, true)(t) createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go index 293977f6bf4..350e7997770 100644 --- a/tests/integration/api_repo_file_delete_test.go +++ b/tests/integration/api_repo_file_delete_test.go @@ -110,56 +110,45 @@ func TestAPIDeleteFile(t *testing.T) { AddTokenAuth(token2) MakeRequest(t, req, http.StatusUnprocessableEntity) - // Test creating a file in repo16 by user4 who does not have write access - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" where user2 is a collaborator - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(org3, repo3, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &deleteFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" with no user token - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(org3, repo3, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &deleteFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) + testCases := []struct { + owner *user_model.User + repo *repo_model.Repository + token string + expectedStatus int + }{ + // Test creating a file in repo16 by user4 who does not have write access + {user2, repo16, token4, http.StatusNotFound}, + // Tests a repo with no token given so will fail + {user2, repo16, "", http.StatusNotFound}, + // Test using access token for a private repo that the user of the token owns + {user2, repo16, token2, http.StatusOK}, + // Test using org repo "org3/repo3" where user2 is a collaborator + {org3, repo3, token2, http.StatusOK}, + // Test using org repo "org3/repo3" with no user token + {org3, repo3, "", http.StatusNotFound}, + // Test using repo "user2/repo1" where user4 is a NOT collaborator + {user2, repo1, token4, http.StatusForbidden}, + } + permissionFiles := map[*repo_model.Repository]map[string]string{} + for i, testCase := range testCases { + if permissionFiles[testCase.repo] == nil { + permissionFiles[testCase.repo] = map[string]string{} + } + permissionFiles[testCase.repo][fmt.Sprintf("delete/file%d.txt", fileID+i+1)] = "This is a NEW file" + } + for _, testCase := range testCases { + if permissionFiles[testCase.repo] != nil { + createFileInBranch(testCase.owner, testCase.repo, createFileInBranchOptions{}, permissionFiles[testCase.repo]) + delete(permissionFiles, testCase.repo) + } + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + deleteFileOptions = getDeleteFileOptions() + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCase.owner.Name, testCase.repo.Name, treePath), &deleteFileOptions) + if testCase.token != "" { + req.AddTokenAuth(testCase.token) + } + MakeRequest(t, req, testCase.expectedStatus) + } }) } diff --git a/tests/integration/api_repo_file_get_test.go b/tests/integration/api_repo_file_get_test.go index a785cd853d5..e2a4260b3b2 100644 --- a/tests/integration/api_repo_file_get_test.go +++ b/tests/integration/api_repo_file_get_test.go @@ -9,25 +9,22 @@ import ( "testing" auth_model "gitea.dev/models/auth" - "gitea.dev/tests" "github.com/stretchr/testify/assert" ) func TestAPIGetRawFileOrLFS(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // Test with raw file - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md") - resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) - - req = NewRequest(t, "GET", "/api/v1/repos/user2/repo2/media/test.xml").AddTokenAuth(getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)) - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) - - // Test with LFS onGiteaRun(t, func(t *testing.T, u *url.URL) { + // Test with raw file + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md") + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) + + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo2/media/test.xml").AddTokenAuth(getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)) + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) + + // Test with LFS createLFSTestRepository(t, "repo-lfs-test") httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository) t.Run("repo-lfs-test", func(t *testing.T) { diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index a4b6cde6893..534d3b7a960 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -235,56 +235,45 @@ func TestAPIUpdateFile(t *testing.T) { apiError := DecodeJSON(t, resp, &context.APIError{}) assert.Equal(t, expectedAPIError, apiError) - // Test creating a file in repo1 by user4 who does not have write access - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" where user2 is a collaborator - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(org3, repo3, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" with no user token - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(org3, repo3, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) + testCases := []struct { + owner *user_model.User + repo *repo_model.Repository + token string + expectedStatus int + }{ + // Test creating a file in repo1 by user4 who does not have write access + {user2, repo16, token4, http.StatusNotFound}, + // Tests a repo with no token given so will fail + {user2, repo16, "", http.StatusNotFound}, + // Test using access token for a private repo that the user of the token owns + {user2, repo16, token2, http.StatusOK}, + // Test using org repo "org3/repo3" where user2 is a collaborator + {org3, repo3, token2, http.StatusOK}, + // Test using org repo "org3/repo3" with no user token + {org3, repo3, "", http.StatusNotFound}, + // Test using repo "user2/repo1" where user4 is a NOT collaborator + {user2, repo1, token4, http.StatusForbidden}, + } + permissionFiles := map[*repo_model.Repository]map[string]string{} + for i, testCase := range testCases { + if permissionFiles[testCase.repo] == nil { + permissionFiles[testCase.repo] = map[string]string{} + } + permissionFiles[testCase.repo][fmt.Sprintf("update/file%d.txt", fileID+i+1)] = "This is a NEW file" + } + for _, testCase := range testCases { + if permissionFiles[testCase.repo] != nil { + createFileInBranch(testCase.owner, testCase.repo, createFileInBranchOptions{}, permissionFiles[testCase.repo]) + delete(permissionFiles, testCase.repo) + } + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + updateFileOptions = getUpdateFileOptions() + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCase.owner.Name, testCase.repo.Name, treePath), &updateFileOptions) + if testCase.token != "" { + req.AddTokenAuth(testCase.token) + } + MakeRequest(t, req, testCase.expectedStatus) + } }) } diff --git a/tests/integration/api_repo_files_change_test.go b/tests/integration/api_repo_files_change_test.go index 4b3b5ccf7b3..f4d846c3510 100644 --- a/tests/integration/api_repo_files_change_test.go +++ b/tests/integration/api_repo_files_change_test.go @@ -128,135 +128,87 @@ func TestAPIChangeFiles(t *testing.T) { }) } - // Test changing files in a new branch + for _, branch := range []struct { + name string + forcePush bool + protected bool + status int + expectedError string + }{ + // Test changing files in a new branch + {"new_branch", false, false, http.StatusCreated, ""}, + // Test fails creating a file in a branch that already exists without force + {"develop", false, false, http.StatusUnprocessableEntity, `"message":"branch already exists [name: develop]"`}, + // Test succeeds creating a file in a branch that already exists with force + {"develop", true, false, http.StatusCreated, ""}, + // Test fails creating a file in a branch that already exists with force and branch protection enabled + {"develop", true, true, http.StatusForbidden, `"message":"Branch develop is protected from force push"`}, + } { + if branch.protected { + protectionReq := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections", &api.BranchProtection{ + RuleName: "develop", + BranchName: "develop", + Priority: 1, + EnablePush: true, + EnableForcePush: false, + }).AddTokenAuth(token2) + MakeRequest(t, protectionReq, http.StatusCreated) + } + changeFilesOptions := getChangeFilesOptions() + changeFilesOptions.BranchName = repo1.DefaultBranch + changeFilesOptions.NewBranchName = branch.name + changeFilesOptions.ForcePush = branch.forcePush + fileID++ + createTreePath := fmt.Sprintf("new/file%d.txt", fileID) + updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) + deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) + changeFilesOptions.Files[0].Path = createTreePath + changeFilesOptions.Files[1].Path = updateTreePath + changeFilesOptions.Files[2].Path = deleteTreePath + createFile(user2, repo1, updateTreePath) + createFile(user2, repo1, deleteTreePath) + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) + req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions). + AddTokenAuth(token2) + resp := MakeRequest(t, req, branch.status) + if branch.expectedError != "" { + assert.Contains(t, resp.Body.String(), branch.expectedError) + continue + } + filesResponse := DecodeJSON(t, resp, &api.FilesResponse{}) + expectedCreateSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" + expectedCreateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/%s/new/file%d.txt", branch.name, fileID) + expectedCreateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/%s/new/file%d.txt", branch.name, fileID) + expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136" + expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/%s/update/file%d.txt", branch.name, fileID) + expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/%s/update/file%d.txt", branch.name, fileID) + assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) + assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) + assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA) + assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) + assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) + assert.Nil(t, filesResponse.Files[2]) + assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) + } + + // Test updating a file and renaming it changeFilesOptions := getChangeFilesOptions() changeFilesOptions.BranchName = repo1.DefaultBranch - changeFilesOptions.NewBranchName = "new_branch" fileID++ - createTreePath := fmt.Sprintf("new/file%d.txt", fileID) updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) + changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]} + changeFilesOptions.Files[0].FromPath = updateTreePath + changeFilesOptions.Files[0].Path = "rename/" + updateTreePath url := fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusCreated) filesResponse := DecodeJSON(t, resp, &api.FilesResponse{}) - expectedCreateSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" - expectedCreateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) - expectedCreateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136" - expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) - expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) - assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA) - assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) - assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) - assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA) - assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) - assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) - assert.Nil(t, filesResponse.Files[2]) - assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) - - // Test fails creating a file in a branch that already exists without force - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.BranchName = repo1.DefaultBranch - changeFilesOptions.NewBranchName = "develop" - changeFilesOptions.ForcePush = false - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) - req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusUnprocessableEntity) - assert.Contains(t, resp.Body.String(), `"message":"branch already exists [name: develop]"`) - - // Test succeeds creating a file in a branch that already exists with force - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.BranchName = repo1.DefaultBranch - changeFilesOptions.NewBranchName = "develop" - changeFilesOptions.ForcePush = true - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) - req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusCreated) - filesResponse = DecodeJSON(t, resp, &api.FilesResponse{}) - expectedCreateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/develop/new/file%d.txt", fileID) - expectedCreateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/develop/new/file%d.txt", fileID) - expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/develop/update/file%d.txt", fileID) - expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/develop/update/file%d.txt", fileID) - assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA) - assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) - assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) - assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA) - assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) - assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) - assert.Nil(t, filesResponse.Files[2]) - assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) - - // Test fails creating a file in a branch that already exists with force and branch protection enabled - protectionReq := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections", &api.BranchProtection{ - RuleName: "develop", - BranchName: "develop", - Priority: 1, - EnablePush: true, - EnableForcePush: false, - }).AddTokenAuth(token2) - MakeRequest(t, protectionReq, http.StatusCreated) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.BranchName = repo1.DefaultBranch - changeFilesOptions.NewBranchName = "develop" - changeFilesOptions.ForcePush = true - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) - req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusForbidden) - assert.Contains(t, resp.Body.String(), `"message":"Branch develop is protected from force push"`) - - // Test updating a file and renaming it - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.BranchName = repo1.DefaultBranch - fileID++ - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, updateTreePath) - changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]} - changeFilesOptions.Files[0].FromPath = updateTreePath - changeFilesOptions.Files[0].Path = "rename/" + updateTreePath - req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusCreated) - filesResponse = DecodeJSON(t, resp, &api.FilesResponse{}) - expectedUpdateSHA = "08bd14b2e2852529157324de9c226b3364e76136" - expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) - expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) + expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) + expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) assert.Equal(t, expectedUpdateSHA, filesResponse.Files[0].SHA) assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL) assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL) @@ -266,9 +218,9 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Message = "" changeFilesOptions.BranchName = repo1.DefaultBranch fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) + createTreePath := fmt.Sprintf("new/file%d.txt", fileID) updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) + deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath @@ -300,92 +252,40 @@ func TestAPIChangeFiles(t *testing.T) { apiError := DecodeJSON(t, resp, &context.APIError{}) assert.Equal(t, expectedAPIError, apiError) - // Test creating a file in repo1 by user4 who does not have write access - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, updateTreePath) - createFile(user2, repo16, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, updateTreePath) - createFile(user2, repo16, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, updateTreePath) - createFile(user2, repo16, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "org3/repo3" where user2 is a collaborator - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(org3, repo3, updateTreePath) - createFile(org3, repo3, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "org3/repo3" with no user token - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(org3, repo3, updateTreePath) - createFile(org3, repo3, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) + for _, permission := range []struct { + owner *user_model.User + repo *repo_model.Repository + token string + status int + }{ + // Test creating a file in repo1 by user4 who does not have write access + {user2, repo16, token4, http.StatusNotFound}, + // Tests a repo with no token given so will fail + {user2, repo16, "", http.StatusNotFound}, + // Test using access token for a private repo that the user of the token owns + {user2, repo16, token2, http.StatusCreated}, + // Test using org repo "org3/repo3" where user2 is a collaborator + {org3, repo3, token2, http.StatusCreated}, + // Test using org repo "org3/repo3" with no user token + {org3, repo3, "", http.StatusNotFound}, + // Test using repo "user2/repo1" where user4 is a NOT collaborator + {user2, repo1, token4, http.StatusForbidden}, + } { + fileID++ + createTreePath = fmt.Sprintf("new/file%d.txt", fileID) + updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) + deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(permission.owner, permission.repo, updateTreePath) + createFile(permission.owner, permission.repo, deleteTreePath) + changeFilesOptions = getChangeFilesOptions() + changeFilesOptions.Files[0].Path = createTreePath + changeFilesOptions.Files[1].Path = updateTreePath + changeFilesOptions.Files[2].Path = deleteTreePath + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", permission.owner.Name, permission.repo.Name), &changeFilesOptions) + if permission.token != "" { + req = req.AddTokenAuth(permission.token) + } + MakeRequest(t, req, permission.status) + } }) } diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go index 887a7366845..efb8e840caf 100644 --- a/tests/integration/api_repo_git_blobs_test.go +++ b/tests/integration/api_repo_git_blobs_test.go @@ -60,11 +60,6 @@ func TestAPIReposGitBlobs(t *testing.T) { AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) - // Test using org repo "org3/repo3" where user2 is a collaborator - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", org3.Name, repo3.Name, repo3ReadmeSHA). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - // Test using org repo "org3/repo3" with no user token req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", org3.Name, repo3ReadmeSHA, repo3.Name) MakeRequest(t, req, http.StatusNotFound) diff --git a/tests/integration/api_repo_git_commits_test.go b/tests/integration/api_repo_git_commits_test.go index 7905e6acb1e..368420b3adb 100644 --- a/tests/integration/api_repo_git_commits_test.go +++ b/tests/integration/api_repo_git_commits_test.go @@ -75,45 +75,56 @@ func TestAPIReposGitCommitList(t *testing.T) { session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - // Test getting commits (Page 1) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo20/commits?not=master&sha=remove-files-a", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + for _, testCase := range []struct { + name, path, total string + commits []string + files [][]string + }{ + // Test getting commits (Page 1) + { + "Page1", "repo20/commits?not=master&sha=remove-files-a", "2", + []string{"cfe3b3c1fd36fba04f9183287b106497e1afe986", "c8e31bc7688741a5287fcde4fbb8fc129ca07027"}, + [][]string{{"link_hi", "test.csv"}, {"test.csv"}}, + }, + // Test getting commits (Page 1) + { + "NotMaster", "repo16/commits", "3", + []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, + [][]string{{"readme.md"}, {"readme.md"}, {"readme.md"}}, + }, + // Test getting commits (Page=1, Branch=good-sign) + { + "DifferentBranch", "repo16/commits?sha=good-sign", "", + []string{"f27c2b2b03dcab38beaf89b0ab4ff61f6de63441"}, + [][]string{{"readme.md"}}, + }, + { + "FileHistory", "repo16/commits?path=readme.md&sha=good-sign", "1", + []string{"f27c2b2b03dcab38beaf89b0ab4ff61f6de63441"}, + [][]string{{"readme.md"}}, + }, + { + "FileHistoryNotOnMaster", "repo20/commits?path=test.csv&sha=add-csv¬=master", "1", + []string{"c8e31bc7688741a5287fcde4fbb8fc129ca07027"}, + [][]string{{"test.csv"}}, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s", user.Name, testCase.path). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + apiData := DecodeJSON(t, resp, []api.Commit{}) - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 2) - assert.Equal(t, "cfe3b3c1fd36fba04f9183287b106497e1afe986", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"link_hi", "test.csv"}, apiData[0].Files) - assert.Equal(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[1].CommitMeta.SHA) - compareCommitFiles(t, []string{"test.csv"}, apiData[1].Files) - - assert.Equal(t, "2", resp.Header().Get("X-Total")) -} - -func TestAPIReposGitCommitListNotMaster(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - // Test getting commits (Page 1) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 3) - assert.Equal(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) - assert.Equal(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", apiData[1].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[1].Files) - assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", apiData[2].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[2].Files) - - assert.Equal(t, "3", resp.Header().Get("X-Total")) + assert.Len(t, apiData, len(testCase.commits)) + for i, sha := range testCase.commits { + assert.Equal(t, sha, apiData[i].CommitMeta.SHA) + compareCommitFiles(t, testCase.files[i], apiData[i].Files) + } + if testCase.total != "" { + assert.Equal(t, testCase.total, resp.Header().Get("X-Total")) + } + }) + } } func TestAPIReposGitCommitListPage2Empty(t *testing.T) { @@ -133,25 +144,6 @@ func TestAPIReposGitCommitListPage2Empty(t *testing.T) { assert.Empty(t, apiData) } -func TestAPIReposGitCommitListDifferentBranch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - // Test getting commits (Page=1, Branch=good-sign) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?sha=good-sign", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 1) - assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) -} - func TestAPIReposGitCommitListWithoutSelectFields(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -197,46 +189,6 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) { resp.Body.String()) } -func TestGetFileHistory(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?path=readme.md&sha=good-sign", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 1) - assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) - - assert.Equal(t, "1", resp.Header().Get("X-Total")) -} - -func TestGetFileHistoryNotOnMaster(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo20/commits?path=test.csv&sha=add-csv¬=master", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 1) - assert.Equal(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"test.csv"}, apiData[0].Files) - - assert.Equal(t, "1", resp.Header().Get("X-Total")) -} - func TestGetFileHistoryEmptyDateRange(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/api_repo_git_hook_test.go b/tests/integration/api_repo_git_hook_test.go index 01a17c7b08b..d5e232d2b4c 100644 --- a/tests/integration/api_repo_git_hook_test.go +++ b/tests/integration/api_repo_git_hook_test.go @@ -28,61 +28,51 @@ func TestAPIGitHooks(t *testing.T) { echo "TestGitHookScript" ` - t.Run("ListGitHooks", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + // user1 is an admin user + adminSession := loginUser(t, "user1") + adminReadToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeReadRepository) + adminWriteToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeWriteRepository) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID}) + ownerSession := loginUser(t, owner1.Name) + ownerReadToken := getTokenForLoggedInUser(t, ownerSession, auth_model.AccessTokenScopeReadRepository) + ownerWriteToken := getTokenForLoggedInUser(t, ownerSession, auth_model.AccessTokenScopeWriteRepository) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + for _, testCase := range []struct { + name string + repoID int64 + }{ + {"ListGitHooks", 37}, + {"NoGitHooks", 1}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - apiGitHooks := DecodeJSON(t, resp, []*api.GitHook{}) - assert.Len(t, apiGitHooks, 3) - for _, apiGitHook := range apiGitHooks { - if apiGitHook.Name == "pre-receive" { - assert.True(t, apiGitHook.IsActive) - assert.Equal(t, testHookContent, apiGitHook.Content) - } else { - assert.False(t, apiGitHook.IsActive) - assert.Empty(t, apiGitHook.Content) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: testCase.repoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). + AddTokenAuth(adminReadToken) + resp := MakeRequest(t, req, http.StatusOK) + apiGitHooks := DecodeJSON(t, resp, []*api.GitHook{}) + assert.Len(t, apiGitHooks, 3) + for _, apiGitHook := range apiGitHooks { + if testCase.repoID == 37 && apiGitHook.Name == "pre-receive" { + assert.True(t, apiGitHook.IsActive) + assert.Equal(t, testHookContent, apiGitHook.Content) + } else { + assert.False(t, apiGitHook.IsActive) + assert.Empty(t, apiGitHook.Content) + } } - } - }) - - t.Run("NoGitHooks", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - apiGitHooks := DecodeJSON(t, resp, []*api.GitHook{}) - assert.Len(t, apiGitHooks, 3) - for _, apiGitHook := range apiGitHooks { - assert.False(t, apiGitHook.IsActive) - assert.Empty(t, apiGitHook.Content) - } - }) + }) + } t.Run("ListGitHooksNoAccess", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). - AddTokenAuth(token) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner1.Name, repo1.Name). + AddTokenAuth(ownerReadToken) MakeRequest(t, req, http.StatusForbidden) }) @@ -92,11 +82,8 @@ echo "TestGitHookScript" repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + AddTokenAuth(adminReadToken) resp := MakeRequest(t, req, http.StatusOK) apiGitHook := DecodeJSON(t, resp, &api.GitHook{}) assert.True(t, apiGitHook.IsActive) @@ -105,13 +92,8 @@ echo "TestGitHookScript" t.Run("GetGitHookNoAccess", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner1.Name, repo1.Name). + AddTokenAuth(ownerReadToken) MakeRequest(t, req, http.StatusForbidden) }) @@ -121,22 +103,18 @@ echo "TestGitHookScript" repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name) req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{ Content: testHookContent, - }).AddTokenAuth(token) + }).AddTokenAuth(adminWriteToken) resp := MakeRequest(t, req, http.StatusOK) apiGitHook := DecodeJSON(t, resp, &api.GitHook{}) assert.True(t, apiGitHook.IsActive) assert.Equal(t, testHookContent, apiGitHook.Content) req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + AddTokenAuth(adminWriteToken) resp = MakeRequest(t, req, http.StatusOK) apiGitHook2 := DecodeJSON(t, resp, &api.GitHook{}) assert.True(t, apiGitHook2.IsActive) @@ -146,15 +124,10 @@ echo "TestGitHookScript" t.Run("EditGitHookNoAccess", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive", owner1.Name, repo1.Name) req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{ Content: testHookContent, - }).AddTokenAuth(token) + }).AddTokenAuth(ownerWriteToken) MakeRequest(t, req, http.StatusForbidden) }) @@ -164,16 +137,12 @@ echo "TestGitHookScript" repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + AddTokenAuth(adminWriteToken) MakeRequest(t, req, http.StatusNoContent) req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + AddTokenAuth(adminWriteToken) resp := MakeRequest(t, req, http.StatusOK) apiGitHook2 := DecodeJSON(t, resp, &api.GitHook{}) assert.False(t, apiGitHook2.IsActive) @@ -183,13 +152,8 @@ echo "TestGitHookScript" t.Run("DeleteGitHookNoAccess", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner1.Name, repo1.Name). + AddTokenAuth(ownerWriteToken) MakeRequest(t, req, http.StatusForbidden) }) } diff --git a/tests/integration/api_repo_git_notes_test.go b/tests/integration/api_repo_git_notes_test.go deleted file mode 100644 index 8cb531e792c..00000000000 --- a/tests/integration/api_repo_git_notes_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "net/url" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - api "gitea.dev/modules/structs" - - "github.com/stretchr/testify/assert" -) - -func TestAPIReposGitNotes(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - // check invalid requests - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/12345", user.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/..", user.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - - // check valid request - req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/65f1bf27bc3bf70f64657658635e66094edbcb4d", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, &api.Note{}) - assert.Equal(t, "This is a test note\n", apiData.Message) - assert.NotEmpty(t, apiData.Commit.Files) - assert.NotNil(t, apiData.Commit.RepoCommit.Verification) - }) -} diff --git a/tests/integration/api_repo_git_ref_test.go b/tests/integration/api_repo_git_ref_test.go deleted file mode 100644 index fc43c4eac69..00000000000 --- a/tests/integration/api_repo_git_ref_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - "gitea.dev/tests" -) - -func TestAPIReposGitRefs(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - for _, ref := range [...]string{ - "refs/heads/master", // Branch - "refs/tags/v1.1", // Tag - } { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/%s", user.Name, ref). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - } - // Test getting all refs - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs", user.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - // Test getting non-existent refs - req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs/heads/unknown", user.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go index 50f20fb3613..0df44637eca 100644 --- a/tests/integration/api_repo_git_tags_test.go +++ b/tests/integration/api_repo_git_tags_test.go @@ -6,6 +6,7 @@ package integration import ( "fmt" "net/http" + "net/url" "strings" "testing" @@ -22,53 +23,106 @@ import ( ) func TestAPIGitTags(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + onGiteaRun(t, func(t *testing.T, _url *url.URL) { + t.Run("Notes", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - // Set up git config for the tagger - _ = git.ManagedConfigSet(t.Context(), repo, "user.name", user.Name) - _ = git.ManagedConfigSet(t.Context(), repo, "user.email", user.Email) + // check invalid requests + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/12345", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) - gitRepo, _ := git.OpenRepository(t.Context(), repo) - defer gitRepo.Close() + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/..", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) - commit, _ := gitRepo.GetBranchCommit(t.Context(), "master") - lTagName := "lightweightTag" - _ = gitRepo.CreateTag(t.Context(), lTagName, commit.ID.String()) + // check valid request + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/65f1bf27bc3bf70f64657658635e66094edbcb4d", user.Name). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - aTagName := "annotatedTag" - aTagMessage := strings.Repeat("very long " /*10bytes*/, 20*1024) + "annotated message" - _ = gitRepo.CreateAnnotatedTag(t.Context(), aTagName, aTagMessage, commit.ID.String()) - aTag, _ := gitRepo.GetTag(t.Context(), aTagName) + apiData := DecodeJSON(t, resp, &api.Note{}) + assert.Equal(t, "This is a test note\n", apiData.Message) + assert.NotEmpty(t, apiData.Commit.Files) + assert.NotNil(t, apiData.Commit.RepoCommit.Verification) + }) - // SHOULD work for annotated tags - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - tag := DecodeJSON(t, resp, &api.AnnotatedTag{}) - assert.Equal(t, aTagName, tag.Tag) - assert.Equal(t, aTag.ID.String(), tag.SHA) - assert.Equal(t, commit.ID.String(), tag.Object.SHA) - assert.Equal(t, aTagMessage+"\n", tag.Message) - assert.Equal(t, user.Name, tag.Tagger.Name) - assert.Equal(t, user.Email, tag.Tagger.Email) - assert.Equal(t, repo.APIURL()+"/git/tags/"+aTag.ID.String(), tag.URL) + t.Run("Refs", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - defer test.MockVariableValue(&git.MaxGitObjectSize, 1024)() - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - tag = DecodeJSON(t, resp, &api.AnnotatedTag{}) - assert.True(t, strings.HasPrefix(aTagMessage, tag.Message)) - assert.Less(t, len(tag.Message), len(aTagMessage)) - assert.Less(t, len(tag.Message), 1024) + for _, ref := range [...]string{ + "refs/heads/master", // Branch + "refs/tags/v1.1", // Tag + } { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/%s", user.Name, ref). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + } + // Test getting all refs + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + // Test getting non-existent refs + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs/heads/unknown", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) - // Should NOT work for lightweight tags - badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, commit.ID.String()). - AddTokenAuth(token) - MakeRequest(t, badReq, http.StatusBadRequest) + t.Run("Tags", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // Set up git config for the tagger + _ = git.ManagedConfigSet(t.Context(), repo, "user.name", user.Name) + _ = git.ManagedConfigSet(t.Context(), repo, "user.email", user.Email) + + gitRepo, _ := git.OpenRepository(t.Context(), repo) + defer gitRepo.Close() + + commit, _ := gitRepo.GetBranchCommit(t.Context(), "master") + lTagName := "lightweightTag" + _ = gitRepo.CreateTag(t.Context(), lTagName, commit.ID.String()) + + aTagName := "annotatedTag" + aTagMessage := strings.Repeat("very long " /*10bytes*/, 20*1024) + "annotated message" + _ = gitRepo.CreateAnnotatedTag(t.Context(), aTagName, aTagMessage, commit.ID.String()) + aTag, _ := gitRepo.GetTag(t.Context(), aTagName) + + // SHOULD work for annotated tags + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + tag := DecodeJSON(t, resp, &api.AnnotatedTag{}) + assert.Equal(t, aTagName, tag.Tag) + assert.Equal(t, aTag.ID.String(), tag.SHA) + assert.Equal(t, commit.ID.String(), tag.Object.SHA) + assert.Equal(t, aTagMessage+"\n", tag.Message) + assert.Equal(t, user.Name, tag.Tagger.Name) + assert.Equal(t, user.Email, tag.Tagger.Email) + assert.Equal(t, repo.APIURL()+"/git/tags/"+aTag.ID.String(), tag.URL) + + defer test.MockVariableValue(&git.MaxGitObjectSize, 1024)() + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + tag = DecodeJSON(t, resp, &api.AnnotatedTag{}) + assert.True(t, strings.HasPrefix(aTagMessage, tag.Message)) + assert.Less(t, len(tag.Message), len(aTagMessage)) + assert.Less(t, len(tag.Message), 1024) + + // Should NOT work for lightweight tags + badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, commit.ID.String()). + AddTokenAuth(token) + MakeRequest(t, badReq, http.StatusBadRequest) + }) + }) } func TestAPIDeleteTagByName(t *testing.T) { diff --git a/tests/integration/api_repo_lfs_locks_test.go b/tests/integration/api_repo_lfs_locks_test.go index f1a38334c1e..2c36092ea06 100644 --- a/tests/integration/api_repo_lfs_locks_test.go +++ b/tests/integration/api_repo_lfs_locks_test.go @@ -21,156 +21,162 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPILFSLocksNotStarted(t *testing.T) { +func TestAPILFSLocks(t *testing.T) { defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, false)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/verify", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/10/unlock", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) -} + t.Run("NotStarted", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, false)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) -func TestAPILFSLocksNotLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/10/unlock", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + }) - req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) - req.Header.Set("Accept", lfs.MediaType) - resp := MakeRequest(t, req, http.StatusUnauthorized) - lfsLockError := DecodeJSON(t, resp, &api.LFSLockError{}) - assert.Equal(t, "You must have pull access to list locks", lfsLockError.Message) -} + t.Run("NotLogin", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) -func TestAPILFSLocksLogged(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // in org 3 - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // in org 3 + req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + req.Header.Set("Accept", lfs.MediaType) + resp := MakeRequest(t, req, http.StatusUnauthorized) + lfsLockError := DecodeJSON(t, resp, &api.LFSLockError{}) + assert.Equal(t, "You must have pull access to list locks", lfsLockError.Message) + }) - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // own by org 3 + t.Run("Logged", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // in org 3 + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // in org 3 - tests := []struct { - user *user_model.User - repo *repo_model.Repository - path string - httpResult int - addTime []int - }{ - {user: user2, repo: repo1, path: "foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{0}}, - {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusCreated, addTime: []int{0}}, - {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusConflict}, - {user: user2, repo: repo1, path: "Foo/BaR.zip", httpResult: http.StatusConflict}, - {user: user2, repo: repo1, path: "Foo/Test/../subFOlder/../Relative/../BaR.zip", httpResult: http.StatusConflict}, - {user: user4, repo: repo1, path: "FoO/BaR.zip", httpResult: http.StatusUnauthorized}, - {user: user4, repo: repo1, path: "path/test-user4", httpResult: http.StatusUnauthorized}, - {user: user2, repo: repo1, path: "patH/Test-user4", httpResult: http.StatusCreated, addTime: []int{0}}, - {user: user2, repo: repo1, path: "some/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/path", httpResult: http.StatusCreated, addTime: []int{0}}, + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // own by org 3 - {user: user2, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{1, 2}}, - {user: user4, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusConflict}, - {user: user4, repo: repo3, path: "test/foo/bar.bin", httpResult: http.StatusCreated, addTime: []int{1, 2}}, - } + tests := []struct { + user *user_model.User + repo *repo_model.Repository + path string + httpResult int + addTime []int + }{ + {user: user2, repo: repo1, path: "foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusConflict}, + {user: user2, repo: repo1, path: "Foo/BaR.zip", httpResult: http.StatusConflict}, + {user: user2, repo: repo1, path: "Foo/Test/../subFOlder/../Relative/../BaR.zip", httpResult: http.StatusConflict}, + {user: user4, repo: repo1, path: "FoO/BaR.zip", httpResult: http.StatusUnauthorized}, + {user: user4, repo: repo1, path: "path/test-user4", httpResult: http.StatusUnauthorized}, + {user: user2, repo: repo1, path: "patH/Test-user4", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "some/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/path", httpResult: http.StatusCreated, addTime: []int{0}}, - resultsTests := []struct { - user *user_model.User - repo *repo_model.Repository - totalCount int - oursCount int - theirsCount int - locksOwners []*user_model.User - locksTimes []time.Time - }{ - {user: user2, repo: repo1, totalCount: 4, oursCount: 4, theirsCount: 0, locksOwners: []*user_model.User{user2, user2, user2, user2}, locksTimes: []time.Time{}}, - {user: user2, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, - {user: user4, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, - } + {user: user2, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{1, 2}}, + {user: user4, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusConflict}, + {user: user4, repo: repo3, path: "test/foo/bar.bin", httpResult: http.StatusCreated, addTime: []int{1, 2}}, + } - deleteTests := []struct { - user *user_model.User - repo *repo_model.Repository - lockID string - }{} + resultsTests := []struct { + user *user_model.User + repo *repo_model.Repository + totalCount int + oursCount int + theirsCount int + locksOwners []*user_model.User + locksTimes []time.Time + }{ + {user: user2, repo: repo1, totalCount: 4, oursCount: 4, theirsCount: 0, locksOwners: []*user_model.User{user2, user2, user2, user2}, locksTimes: []time.Time{}}, + {user: user2, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, + {user: user4, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, + } - // create locks - for _, test := range tests { - session := loginUser(t, test.user.Name) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path}) - req.Header.Set("Accept", lfs.AcceptHeader) - req.Header.Set("Content-Type", lfs.MediaType) - resp := session.MakeRequest(t, req, test.httpResult) - if len(test.addTime) > 0 { - lfsLock := DecodeJSON(t, resp, &api.LFSLockResponse{}) - assert.Equal(t, test.user.Name, lfsLock.Lock.Owner.Name) - assert.Equal(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second - for _, id := range test.addTime { - resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) + deleteTests := []struct { + user *user_model.User + repo *repo_model.Repository + lockID string + }{} + + sessions := map[string]*TestSession{ + user2.Name: loginUser(t, user2.Name), + user4.Name: loginUser(t, user4.Name), + } + + // create locks + for _, test := range tests { + session := sessions[test.user.Name] + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path}) + req.Header.Set("Accept", lfs.AcceptHeader) + req.Header.Set("Content-Type", lfs.MediaType) + resp := session.MakeRequest(t, req, test.httpResult) + if len(test.addTime) > 0 { + lfsLock := DecodeJSON(t, resp, &api.LFSLockResponse{}) + assert.Equal(t, test.user.Name, lfsLock.Lock.Owner.Name) + assert.Equal(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + for _, id := range test.addTime { + resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) + } } } - } - // check creation - for _, test := range resultsTests { - session := loginUser(t, test.user.Name) - req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) - req.Header.Set("Accept", lfs.AcceptHeader) - resp := session.MakeRequest(t, req, http.StatusOK) - lfsLocks := DecodeJSON(t, resp, &api.LFSLockList{}) - assert.Len(t, lfsLocks.Locks, test.totalCount) - for i, lock := range lfsLocks.Locks { - assert.Equal(t, test.locksOwners[i].Name, lock.Owner.Name) - assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second) - assert.Equal(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + // check creation + for _, test := range resultsTests { + session := sessions[test.user.Name] + req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) + req.Header.Set("Accept", lfs.AcceptHeader) + resp := session.MakeRequest(t, req, http.StatusOK) + lfsLocks := DecodeJSON(t, resp, &api.LFSLockList{}) + assert.Len(t, lfsLocks.Locks, test.totalCount) + for i, lock := range lfsLocks.Locks { + assert.Equal(t, test.locksOwners[i].Name, lock.Owner.Name) + assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second) + assert.Equal(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + } + + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) + req.Header.Set("Accept", lfs.AcceptHeader) + req.Header.Set("Content-Type", lfs.MediaType) + resp = session.MakeRequest(t, req, http.StatusOK) + lfsLocksVerify := DecodeJSON(t, resp, &api.LFSLockListVerify{}) + assert.Len(t, lfsLocksVerify.Ours, test.oursCount) + assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount) + for _, lock := range lfsLocksVerify.Ours { + assert.Equal(t, test.user.Name, lock.Owner.Name) + deleteTests = append(deleteTests, struct { + user *user_model.User + repo *repo_model.Repository + lockID string + }{test.user, test.repo, lock.ID}) + } + for _, lock := range lfsLocksVerify.Theirs { + assert.NotEqual(t, test.user.DisplayName(), lock.Owner.Name) + } } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) - req.Header.Set("Accept", lfs.AcceptHeader) - req.Header.Set("Content-Type", lfs.MediaType) - resp = session.MakeRequest(t, req, http.StatusOK) - lfsLocksVerify := DecodeJSON(t, resp, &api.LFSLockListVerify{}) - assert.Len(t, lfsLocksVerify.Ours, test.oursCount) - assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount) - for _, lock := range lfsLocksVerify.Ours { - assert.Equal(t, test.user.Name, lock.Owner.Name) - deleteTests = append(deleteTests, struct { - user *user_model.User - repo *repo_model.Repository - lockID string - }{test.user, test.repo, lock.ID}) + // remove all locks + for _, test := range deleteTests { + session := sessions[test.user.Name] + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/%s/unlock", test.repo.FullName(), test.lockID), map[string]string{}) + req.Header.Set("Accept", lfs.AcceptHeader) + req.Header.Set("Content-Type", lfs.MediaType) + resp := session.MakeRequest(t, req, http.StatusOK) + lfsLockRep := DecodeJSON(t, resp, &api.LFSLockResponse{}) + assert.Equal(t, test.lockID, lfsLockRep.Lock.ID) + assert.Equal(t, test.user.Name, lfsLockRep.Lock.Owner.Name) } - for _, lock := range lfsLocksVerify.Theirs { - assert.NotEqual(t, test.user.DisplayName(), lock.Owner.Name) + + // check that we don't have any lock + for _, test := range resultsTests { + session := sessions[test.user.Name] + req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) + req.Header.Set("Accept", lfs.AcceptHeader) + resp := session.MakeRequest(t, req, http.StatusOK) + lfsLocks := DecodeJSON(t, resp, &api.LFSLockList{}) + assert.Empty(t, lfsLocks.Locks) } - } - - // remove all locks - for _, test := range deleteTests { - session := loginUser(t, test.user.Name) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/%s/unlock", test.repo.FullName(), test.lockID), map[string]string{}) - req.Header.Set("Accept", lfs.AcceptHeader) - req.Header.Set("Content-Type", lfs.MediaType) - resp := session.MakeRequest(t, req, http.StatusOK) - lfsLockRep := DecodeJSON(t, resp, &api.LFSLockResponse{}) - assert.Equal(t, test.lockID, lfsLockRep.Lock.ID) - assert.Equal(t, test.user.Name, lfsLockRep.Lock.Owner.Name) - } - - // check that we don't have any lock - for _, test := range resultsTests { - session := loginUser(t, test.user.Name) - req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) - req.Header.Set("Accept", lfs.AcceptHeader) - resp := session.MakeRequest(t, req, http.StatusOK) - lfsLocks := DecodeJSON(t, resp, &api.LFSLockList{}) - assert.Empty(t, lfsLocks.Locks) - } + }) } diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go index dea02814dfb..629a57fc1be 100644 --- a/tests/integration/api_repo_lfs_test.go +++ b/tests/integration/api_repo_lfs_test.go @@ -25,38 +25,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestAPILFSNotStarted(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, false)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "PUT", "/%s/%s.git/info/lfs/objects/oid/10", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid/name", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestAPILFSMediaType(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) - MakeRequest(t, req, http.StatusUnsupportedMediaType) - req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) - MakeRequest(t, req, http.StatusUnsupportedMediaType) -} - func createLFSTestRepository(t *testing.T, repoName string) *repo_model.Repository { t.Helper() ctx := NewAPITestContext(t, "user2", repoName, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) @@ -68,428 +36,415 @@ func createLFSTestRepository(t *testing.T, repoName string) *repo_model.Reposito return repo } -func TestAPILFSBatch(t *testing.T) { +func TestAPILFS(t *testing.T) { defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - repo := createLFSTestRepository(t, "lfs-batch-repo") + t.Run("NotStarted", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, false)() - oid := storeObjectInRepo(t, repo.ID, "dummy1") - defer git_model.RemoveLFSMetaObjectByOid(t.Context(), repo.ID, oid) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - session := loginUser(t, "user2") - - newRequest := func(t testing.TB, br *lfs.BatchRequest) *RequestWrapper { - return NewRequestWithJSON(t, "POST", "/user2/lfs-batch-repo.git/info/lfs/objects/batch", br). - SetHeader("Accept", lfs.AcceptHeader). - SetHeader("Content-Type", lfs.MediaType) - } - decodeResponse := func(t *testing.T, b *bytes.Buffer) *lfs.BatchResponse { - var br lfs.BatchResponse - - assert.NoError(t, json.Unmarshal(b.Bytes(), &br)) - return &br - } - - t.Run("InvalidJsonRequest", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, nil) - - session.MakeRequest(t, req, http.StatusBadRequest) + req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "PUT", "/%s/%s.git/info/lfs/objects/oid/10", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid/name", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) }) - t.Run("InvalidOperation", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("MediaType", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() - req := newRequest(t, &lfs.BatchRequest{ - Operation: "dummy", + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) + MakeRequest(t, req, http.StatusUnsupportedMediaType) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusUnsupportedMediaType) + }) + + t.Run("Batch", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + + repo := createLFSTestRepository(t, "lfs-batch-repo") + + oid := storeObjectInRepo(t, repo.ID, "dummy1") + defer git_model.RemoveLFSMetaObjectByOid(t.Context(), repo.ID, oid) + + session := loginUser(t, "user2") + + newRequest := func(t testing.TB, br *lfs.BatchRequest) *RequestWrapper { + return NewRequestWithJSON(t, "POST", "/user2/lfs-batch-repo.git/info/lfs/objects/batch", br). + SetHeader("Accept", lfs.AcceptHeader). + SetHeader("Content-Type", lfs.MediaType) + } + makeRequest := func(t *testing.T, request *lfs.BatchRequest) *lfs.BatchResponse { + resp := session.MakeRequest(t, newRequest(t, request), http.StatusOK) + var br lfs.BatchResponse + + assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), &br)) + return &br + } + + t.Run("InvalidJsonRequest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, nil) + + session.MakeRequest(t, req, http.StatusBadRequest) }) - session.MakeRequest(t, req, http.StatusBadRequest) - }) - - t.Run("InvalidPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{ - {Oid: "dummy"}, - {Oid: oid, Size: -1}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 2) - assert.Equal(t, "dummy", br.Objects[0].Oid) - assert.Equal(t, oid, br.Objects[1].Oid) - assert.Equal(t, int64(0), br.Objects[0].Size) - assert.Equal(t, int64(-1), br.Objects[1].Size) - assert.NotNil(t, br.Objects[0].Error) - assert.NotNil(t, br.Objects[1].Error) - assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) - assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[1].Error.Code) - assert.Equal(t, "Oid or size are invalid", br.Objects[0].Error.Message) - assert.Equal(t, "Oid or size are invalid", br.Objects[1].Error.Message) - }) - - t.Run("PointerSizeMismatch", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{ - {Oid: oid, Size: 1}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.NotNil(t, br.Objects[0].Error) - assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) - assert.Equal(t, "Object "+oid+" is not 1 bytes", br.Objects[0].Error.Message) - }) - - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("PointerNotInStore", func(t *testing.T) { + t.Run("InvalidOperation", func(t *testing.T) { defer tests.PrintCurrentTest(t)() req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{ - {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, - }, + Operation: "dummy", }) - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.NotNil(t, br.Objects[0].Error) - assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) + session.MakeRequest(t, req, http.StatusBadRequest) }) - t.Run("MetaNotFound", func(t *testing.T) { + t.Run("InvalidPointer", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: "dummy"}, + {Oid: oid, Size: -1}, + }, + }) + assert.Len(t, br.Objects, 2) + assert.Equal(t, "dummy", br.Objects[0].Oid) + assert.Equal(t, oid, br.Objects[1].Oid) + assert.Equal(t, int64(0), br.Objects[0].Size) + assert.Equal(t, int64(-1), br.Objects[1].Size) + assert.NotNil(t, br.Objects[0].Error) + assert.NotNil(t, br.Objects[1].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[1].Error.Code) + assert.Equal(t, "Oid or size are invalid", br.Objects[0].Error.Message) + assert.Equal(t, "Oid or size are invalid", br.Objects[1].Error.Message) + }) + + t.Run("PointerSizeMismatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 1}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, "Object "+oid+" is not 1 bytes", br.Objects[0].Error.Message) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("PointerNotInStore", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) + }) + + t.Run("MetaNotFound", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.False(t, exist) + err = contentStore.Put(p, bytes.NewReader([]byte("dummy0"))) + assert.NoError(t, err) + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{p}, + }) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) + }) + + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 6}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Contains(t, br.Objects[0].Actions, "download") + l := br.Objects[0].Actions["download"] + assert.NotNil(t, l) + assert.NotEmpty(t, l.Href) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("FileTooBig", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.LFS.MaxFileSize, 2)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, "Size must be less than or equal to 2", br.Objects[0].Error.Message) + }) + + t.Run("CrossRepoObjectRequiresUpload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // An object whose bytes already exist in the store but which is not + // linked to this repo must not be silently linked, even when the + // caller can access it in another repo. Auto-linking let a deploy key + // (whose token carries the repo owner's identity) exfiltrate objects + // across repos without proving possession. The client must upload. + p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.True(t, exist) + + // The object is linked to another repo owned by the same user. + repo2 := createLFSTestRepository(t, "lfs-batch2-repo") + storeObjectInRepo(t, repo2.ID, "dummy0") + + meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + assert.Nil(t, meta) + assert.Equal(t, git_model.ErrLFSObjectNotExist, err) + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{p}, + }) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + // The client is told to upload instead of the object being linked. + assert.Contains(t, br.Objects[0].Actions, "upload") + + // No meta object may have been created for this repo. + meta, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + assert.Nil(t, meta) + assert.Equal(t, git_model.ErrLFSObjectNotExist, err) + + // Cleanup + err = contentStore.ObjectStorage.Delete(p.RelativePath()) + assert.NoError(t, err) + }) + + t.Run("AlreadyExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 6}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Empty(t, br.Objects[0].Actions) + }) + + t.Run("NewFile", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: "d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0153", Size: 1}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Contains(t, br.Objects[0].Actions, "upload") + ul := br.Objects[0].Actions["upload"] + assert.NotNil(t, ul) + assert.NotEmpty(t, ul.Href) + assert.Equal(t, "chunked", ul.Header["Transfer-Encoding"], "git-lfs client needs Transfer-Encoding to do chunked transfer") + assert.Contains(t, br.Objects[0].Actions, "verify") + vl := br.Objects[0].Actions["verify"] + assert.NotNil(t, vl) + assert.NotEmpty(t, vl.Href) + }) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + + repo := createLFSTestRepository(t, "lfs-upload-repo") + session := loginUser(t, "user2") + + newRequest := func(t testing.TB, p lfs.Pointer, content string) *RequestWrapper { + reqUrl := fmt.Sprintf("/user2/lfs-upload-repo.git/info/lfs/objects/%s/%d", p.Oid, p.Size) + return NewRequestWithBody(t, "PUT", reqUrl, strings.NewReader(content)) + } + + t.Run("InvalidPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, lfs.Pointer{Oid: "dummy"}, "") + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("AlreadyExistsInStore", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "83de2e488b89a0aa1c97496b888120a28b0c1e15463a4adb8405578c540f36d4", Size: 6} contentStore := lfs.NewContentStore() exist, err := contentStore.Exists(p) assert.NoError(t, err) assert.False(t, exist) - err = contentStore.Put(p, bytes.NewReader([]byte("dummy0"))) + err = contentStore.Put(p, bytes.NewReader([]byte("dummy5"))) assert.NoError(t, err) - req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{p}, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.NotNil(t, br.Objects[0].Error) - assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) - }) - - t.Run("Success", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{ - {Oid: oid, Size: 6}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.Nil(t, br.Objects[0].Error) - assert.Contains(t, br.Objects[0].Actions, "download") - l := br.Objects[0].Actions["download"] - assert.NotNil(t, l) - assert.NotEmpty(t, l.Href) - }) - }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("FileTooBig", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - defer test.MockVariableValue(&setting.LFS.MaxFileSize, 2)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "upload", - Objects: []lfs.Pointer{ - {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.NotNil(t, br.Objects[0].Error) - assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) - assert.Equal(t, "Size must be less than or equal to 2", br.Objects[0].Error.Message) - }) - - t.Run("CrossRepoObjectRequiresUpload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - // An object whose bytes already exist in the store but which is not - // linked to this repo must not be silently linked, even when the - // caller can access it in another repo. Auto-linking let a deploy key - // (whose token carries the repo owner's identity) exfiltrate objects - // across repos without proving possession. The client must upload. - p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} - - contentStore := lfs.NewContentStore() - exist, err := contentStore.Exists(p) - assert.NoError(t, err) - assert.True(t, exist) - - // The object is linked to another repo owned by the same user. - repo2 := createLFSTestRepository(t, "lfs-batch2-repo") - storeObjectInRepo(t, repo2.ID, "dummy0") - meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) assert.Nil(t, meta) assert.Equal(t, git_model.ErrLFSObjectNotExist, err) - req := newRequest(t, &lfs.BatchRequest{ - Operation: "upload", - Objects: []lfs.Pointer{p}, + t.Run("InvalidAccess", func(t *testing.T) { + req := newRequest(t, p, "invalid") + session.MakeRequest(t, req, http.StatusUnprocessableEntity) }) - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.Nil(t, br.Objects[0].Error) - // The client is told to upload instead of the object being linked. - assert.Contains(t, br.Objects[0].Actions, "upload") + t.Run("ValidAccess", func(t *testing.T) { + req := newRequest(t, p, "dummy5") - // No meta object may have been created for this repo. - meta, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) - assert.Nil(t, meta) - assert.Equal(t, git_model.ErrLFSObjectNotExist, err) + session.MakeRequest(t, req, http.StatusOK) + meta, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + assert.NoError(t, err) + assert.NotNil(t, meta) + }) // Cleanup err = contentStore.ObjectStorage.Delete(p.RelativePath()) assert.NoError(t, err) }) - t.Run("AlreadyExists", func(t *testing.T) { + t.Run("MetaAlreadyExists", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "upload", - Objects: []lfs.Pointer{ - {Oid: oid, Size: 6}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.Nil(t, br.Objects[0].Error) - assert.Empty(t, br.Objects[0].Actions) + oid := storeObjectInRepo(t, repo.ID, "123456") + req := newRequest(t, lfs.Pointer{Oid: oid, Size: 6}, "") + session.MakeRequest(t, req, http.StatusOK) }) - t.Run("NewFile", func(t *testing.T) { + t.Run("HashMismatch", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := newRequest(t, &lfs.BatchRequest{ - Operation: "upload", - Objects: []lfs.Pointer{ - {Oid: "d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0153", Size: 1}, - }, - }) + req := newRequest(t, lfs.Pointer{Oid: "2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a", Size: 1}, "a") - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.Nil(t, br.Objects[0].Error) - assert.Contains(t, br.Objects[0].Actions, "upload") - ul := br.Objects[0].Actions["upload"] - assert.NotNil(t, ul) - assert.NotEmpty(t, ul.Href) - assert.Equal(t, "chunked", ul.Header["Transfer-Encoding"], "git-lfs client needs Transfer-Encoding to do chunked transfer") - assert.Contains(t, br.Objects[0].Actions, "verify") - vl := br.Objects[0].Actions["verify"] - assert.NotNil(t, vl) - assert.NotEmpty(t, vl.Href) - }) - }) -} - -func TestAPILFSUpload(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - - repo := createLFSTestRepository(t, "lfs-upload-repo") - session := loginUser(t, "user2") - - newRequest := func(t testing.TB, p lfs.Pointer, content string) *RequestWrapper { - reqUrl := fmt.Sprintf("/user2/lfs-upload-repo.git/info/lfs/objects/%s/%d", p.Oid, p.Size) - return NewRequestWithBody(t, "PUT", reqUrl, strings.NewReader(content)) - } - - t.Run("InvalidPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, lfs.Pointer{Oid: "dummy"}, "") - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - - t.Run("AlreadyExistsInStore", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - p := lfs.Pointer{Oid: "83de2e488b89a0aa1c97496b888120a28b0c1e15463a4adb8405578c540f36d4", Size: 6} - - contentStore := lfs.NewContentStore() - exist, err := contentStore.Exists(p) - assert.NoError(t, err) - assert.False(t, exist) - err = contentStore.Put(p, bytes.NewReader([]byte("dummy5"))) - assert.NoError(t, err) - - meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) - assert.Nil(t, meta) - assert.Equal(t, git_model.ErrLFSObjectNotExist, err) - - t.Run("InvalidAccess", func(t *testing.T) { - req := newRequest(t, p, "invalid") session.MakeRequest(t, req, http.StatusUnprocessableEntity) }) - t.Run("ValidAccess", func(t *testing.T) { - req := newRequest(t, p, "dummy5") + t.Run("SizeMismatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - session.MakeRequest(t, req, http.StatusOK) - meta, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + req := newRequest(t, lfs.Pointer{Oid: "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", Size: 2}, "a") + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("ConcurrentFailureKeepsExistingMeta", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + pointer, err := lfs.GeneratePointer(strings.NewReader("any-content")) + assert.NoError(t, err) + + // mock a record in database: it should not happen in real world (no existing file in the store) + // !!for testing purpose only!! to verify a failed upload should not remove a valid record, + _, err = git_model.NewLFSMetaObject(t.Context(), repo.ID, pointer) + assert.NoError(t, err) + + // make an invalid request, the existing lfs record should not be removed + req := newRequest(t, lfs.Pointer{Oid: pointer.Oid, Size: 1}, "invalid content") + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, pointer.Oid) assert.NoError(t, err) assert.NotNil(t, meta) }) - // Cleanup - err = contentStore.ObjectStorage.Delete(p.RelativePath()) - assert.NoError(t, err) + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d", Size: 5} + + req := newRequest(t, p, "gitea") + + session.MakeRequest(t, req, http.StatusOK) + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.True(t, exist) + + meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + assert.NoError(t, err) + assert.NotNil(t, meta) + }) }) - t.Run("MetaAlreadyExists", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - oid := storeObjectInRepo(t, repo.ID, "123456") - req := newRequest(t, lfs.Pointer{Oid: oid, Size: 6}, "") - session.MakeRequest(t, req, http.StatusOK) - }) + t.Run("Verify", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() - t.Run("HashMismatch", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + repo := createLFSTestRepository(t, "lfs-verify-repo") + oid := storeObjectInRepo(t, repo.ID, "dummy3") + defer git_model.RemoveLFSMetaObjectByOid(t.Context(), repo.ID, oid) - req := newRequest(t, lfs.Pointer{Oid: "2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a", Size: 1}, "a") + session := loginUser(t, "user2") - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) + for _, testCase := range []struct { + name string + ptr *lfs.Pointer + status int + }{ + {"InvalidJsonRequest", nil, http.StatusUnprocessableEntity}, + {"InvalidPointer", &lfs.Pointer{}, http.StatusUnprocessableEntity}, + {"PointerNotExisting", &lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, http.StatusNotFound}, + {"Success", &lfs.Pointer{Oid: oid, Size: 6}, http.StatusOK}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("SizeMismatch", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, lfs.Pointer{Oid: "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", Size: 2}, "a") - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - - t.Run("ConcurrentFailureKeepsExistingMeta", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - pointer, err := lfs.GeneratePointer(strings.NewReader("any-content")) - assert.NoError(t, err) - - // mock a record in database: it should not happen in real world (no existing file in the store) - // !!for testing purpose only!! to verify a failed upload should not remove a valid record, - _, err = git_model.NewLFSMetaObject(t.Context(), repo.ID, pointer) - assert.NoError(t, err) - - // make an invalid request, the existing lfs record should not be removed - req := newRequest(t, lfs.Pointer{Oid: pointer.Oid, Size: 1}, "invalid content") - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, pointer.Oid) - assert.NoError(t, err) - assert.NotNil(t, meta) - }) - - t.Run("Success", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - p := lfs.Pointer{Oid: "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d", Size: 5} - - req := newRequest(t, p, "gitea") - - session.MakeRequest(t, req, http.StatusOK) - - contentStore := lfs.NewContentStore() - exist, err := contentStore.Exists(p) - assert.NoError(t, err) - assert.True(t, exist) - - meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) - assert.NoError(t, err) - assert.NotNil(t, meta) - }) -} - -func TestAPILFSVerify(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - - repo := createLFSTestRepository(t, "lfs-verify-repo") - oid := storeObjectInRepo(t, repo.ID, "dummy3") - defer git_model.RemoveLFSMetaObjectByOid(t.Context(), repo.ID, oid) - - session := loginUser(t, "user2") - - newRequest := func(t testing.TB, p *lfs.Pointer) *RequestWrapper { - return NewRequestWithJSON(t, "POST", "/user2/lfs-verify-repo.git/info/lfs/verify", p). - SetHeader("Accept", lfs.AcceptHeader). - SetHeader("Content-Type", lfs.MediaType) - } - - t.Run("InvalidJsonRequest", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, nil) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - - t.Run("InvalidPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.Pointer{}) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - - t.Run("PointerNotExisting", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}) - - session.MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("Success", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.Pointer{Oid: oid, Size: 6}) - - session.MakeRequest(t, req, http.StatusOK) + req := NewRequestWithJSON(t, "POST", "/user2/lfs-verify-repo.git/info/lfs/verify", testCase.ptr). + SetHeader("Accept", lfs.AcceptHeader). + SetHeader("Content-Type", lfs.MediaType) + session.MakeRequest(t, req, testCase.status) + }) + } }) } diff --git a/tests/integration/api_repo_secrets_test.go b/tests/integration/api_repo_secrets_test.go index 580b4736b10..b3987da25c2 100644 --- a/tests/integration/api_repo_secrets_test.go +++ b/tests/integration/api_repo_secrets_test.go @@ -30,75 +30,38 @@ func TestAPIRepoSecrets(t *testing.T) { MakeRequest(t, req, http.StatusOK) }) - t.Run("Create", func(t *testing.T) { - cases := []struct { - Name string - ExpectedStatus int - }{ - { - Name: "", - ExpectedStatus: http.StatusMethodNotAllowed, - }, - { - Name: "-", - ExpectedStatus: http.StatusBadRequest, - }, - { - Name: "_", - ExpectedStatus: http.StatusCreated, - }, - { - Name: "secret", - ExpectedStatus: http.StatusCreated, - }, - { - Name: "2secret", - ExpectedStatus: http.StatusBadRequest, - }, - { - Name: "GITEA_secret", - ExpectedStatus: http.StatusBadRequest, - }, - { - Name: "GITHUB_secret", - ExpectedStatus: http.StatusBadRequest, - }, - } - - for _, c := range cases { - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), c.Name), api.CreateOrUpdateSecretOption{ - Data: "data", - }).AddTokenAuth(token) - MakeRequest(t, req, c.ExpectedStatus) - } - }) - - t.Run("CreateWithDescription", func(t *testing.T) { - cases := []struct { - Name string - Description string - ExpectedStatus int - }{ - { - Name: "no_description", - Description: "", - ExpectedStatus: http.StatusCreated, - }, - { - Name: "description", - Description: "some description", - ExpectedStatus: http.StatusCreated, - }, - } - - for _, c := range cases { - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), c.Name), api.CreateOrUpdateSecretOption{ - Data: "data", - Description: c.Description, - }).AddTokenAuth(token) - MakeRequest(t, req, c.ExpectedStatus) - } - }) + type secretCase struct { + Name, Description string + ExpectedStatus int + } + for _, group := range []struct { + name string + cases []secretCase + }{ + {"Create", []secretCase{ + {"", "", http.StatusMethodNotAllowed}, + {"-", "", http.StatusBadRequest}, + {"_", "", http.StatusCreated}, + {"secret", "", http.StatusCreated}, + {"2secret", "", http.StatusBadRequest}, + {"GITEA_secret", "", http.StatusBadRequest}, + {"GITHUB_secret", "", http.StatusBadRequest}, + }}, + {"CreateWithDescription", []secretCase{ + {"no_description", "", http.StatusCreated}, + {"description", "some description", http.StatusCreated}, + }}, + } { + t.Run(group.name, func(t *testing.T) { + for _, c := range group.cases { + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), c.Name), api.CreateOrUpdateSecretOption{ + Data: "data", + Description: c.Description, + }).AddTokenAuth(token) + MakeRequest(t, req, c.ExpectedStatus) + } + }) + } t.Run("Update", func(t *testing.T) { name := "update_secret" diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 79859a28190..b723901fc5f 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -27,358 +27,419 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIUserRepos(t *testing.T) { +func TestAPIRepoFixtureReads(t *testing.T) { defer tests.PrepareTestEnv(t)() - userOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - userOther := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - targetUser := userOwner - testCases := []struct { - name string - doer *user_model.User - count int - privateCount int - }{ - {name: "Anonymous", doer: nil, count: 7}, - {name: "UserOwner", doer: userOwner, count: 14, privateCount: 7}, - {name: "UserOther", doer: userOther, count: 7}, - } + t.Run("UserRepos", func(t *testing.T) { + userOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + userOther := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + targetUser := userOwner - for _, tc := range testCases { - var token string - if tc.doer != nil { - session := loginUser(t, tc.doer.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + testCases := []struct { + name string + doer *user_model.User + count int + privateCount int + }{ + {name: "Anonymous", doer: nil, count: 7}, + {name: "UserOwner", doer: userOwner, count: 14, privateCount: 7}, + {name: "UserOther", doer: userOther, count: 7}, } - t.Run(tc.name, func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", targetUser.Name).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiRepos := DecodeJSON(t, resp, []*api.Repository{}) - assert.Len(t, apiRepos, tc.count) - privateCount := 0 - for _, repo := range apiRepos { - assert.Equal(t, targetUser.ID, repo.Owner.ID) - if repo.Private { - privateCount++ - } + for _, tc := range testCases { + var token string + if tc.doer != nil { + session := loginUser(t, tc.doer.Name) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) } - assert.Equal(t, tc.privateCount, privateCount) - }) - } -} -func TestAPISearchRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - const keyword = "test" + t.Run(tc.name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", targetUser.Name).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - req := NewRequestf(t, "GET", "/api/v1/repos/search?q=%s", keyword) - resp := MakeRequest(t, req, http.StatusOK) + apiRepos := DecodeJSON(t, resp, []*api.Repository{}) + assert.Len(t, apiRepos, tc.count) + privateCount := 0 + for _, repo := range apiRepos { + assert.Equal(t, targetUser.ID, repo.Owner.ID) + if repo.Private { + privateCount++ + } + } + assert.Equal(t, tc.privateCount, privateCount) + }) + } + }) - body := DecodeJSON(t, resp, &api.SearchResults{}) - assert.NotEmpty(t, body.Data) - for _, repo := range body.Data { - assert.Contains(t, repo.Name, keyword) - assert.False(t, repo.Private) - } + t.Run("SearchRepo", func(t *testing.T) { + const keyword = "test" - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 18}) - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}) - orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) + req := NewRequestf(t, "GET", "/api/v1/repos/search?q=%s", keyword) + resp := MakeRequest(t, req, http.StatusOK) - defer test.MockVariableValue(&setting.API.DefaultPagingNum, 10)() + body := DecodeJSON(t, resp, &api.SearchResults{}) + assert.NotEmpty(t, body.Data) + for _, repo := range body.Data { + assert.Contains(t, repo.Name, keyword) + assert.False(t, repo.Private) + } - // Map of expected results, where key is user for login - type expectedResults map[*user_model.User]struct { - count int - repoOwnerID int64 - repoName string - includesPrivate bool - } + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 18}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}) + orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) - testCases := []struct { - name, requestURL string - expectedResults - }{ - { - name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ - nil: {count: 36}, - user: {count: 36}, - user2: {count: 36}, + defer test.MockVariableValue(&setting.API.DefaultPagingNum, 10)() + + // Map of expected results, where key is user for login + type expectedResults map[*user_model.User]struct { + count int + repoOwnerID int64 + repoName string + includesPrivate bool + } + + testCases := []struct { + name, requestURL string + expectedResults + }{ + { + name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ + nil: {count: 36}, + user: {count: 36}, + user2: {count: 36}, + }, }, - }, - { - name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{ - nil: {count: 10}, - user: {count: 10}, - user2: {count: 10}, + { + name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{ + nil: {count: 10}, + user: {count: 10}, + user2: {count: 10}, + }, }, - }, - { - name: "RepositoriesDefault", requestURL: "/api/v1/repos/search?default&private=false", expectedResults: expectedResults{ - nil: {count: 10}, - user: {count: 10}, - user2: {count: 10}, + { + name: "RepositoriesDefault", requestURL: "/api/v1/repos/search?default&private=false", expectedResults: expectedResults{ + nil: {count: 10}, + user: {count: 10}, + user2: {count: 10}, + }, }, - }, - { - name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "big_test_"), expectedResults: expectedResults{ - nil: {count: 7, repoName: "big_test_"}, - user: {count: 7, repoName: "big_test_"}, - user2: {count: 7, repoName: "big_test_"}, + { + name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "big_test_"), expectedResults: expectedResults{ + nil: {count: 7, repoName: "big_test_"}, + user: {count: 7, repoName: "big_test_"}, + user2: {count: 7, repoName: "big_test_"}, + }, }, - }, - { - name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "user2/big_test_"), expectedResults: expectedResults{ - user2: {count: 2, repoName: "big_test_"}, + { + name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "user2/big_test_"), expectedResults: expectedResults{ + user2: {count: 2, repoName: "big_test_"}, + }, }, - }, - { - name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{ - nil: {count: 5}, - user: {count: 9, includesPrivate: true}, - user2: {count: 6, includesPrivate: true}, + { + name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{ + nil: {count: 5}, + user: {count: 9, includesPrivate: true}, + user2: {count: 6, includesPrivate: true}, + }, }, - }, - { - name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 2, includesPrivate: true}, - user2: {count: 2, includesPrivate: true}, - user4: {count: 1}, + { + name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 2, includesPrivate: true}, + user2: {count: 2, includesPrivate: true}, + user4: {count: 1}, + }, }, - }, - { - name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", org3.ID), expectedResults: expectedResults{ - nil: {count: 1}, + { + name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", org3.ID), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 4, includesPrivate: true}, + user2: {count: 3, includesPrivate: true}, + org3: {count: 4, includesPrivate: true}, + }, + }, + { + name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{ + nil: {count: 1, repoOwnerID: orgUser.ID}, + user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true}, + user2: {count: 1, repoOwnerID: orgUser.ID}, + }, + }, + {name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{ + nil: {count: 3}, user: {count: 4, includesPrivate: true}, - user2: {count: 3, includesPrivate: true}, - org3: {count: 4, includesPrivate: true}, - }, - }, - { - name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{ - nil: {count: 1, repoOwnerID: orgUser.ID}, - user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true}, - user2: {count: 1, repoOwnerID: orgUser.ID}, - }, - }, - {name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{ - nil: {count: 3}, - user: {count: 4, includesPrivate: true}, - user4: {count: 7, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeSource", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "source"), expectedResults: expectedResults{ - nil: {count: 0}, - user: {count: 1, includesPrivate: true}, - user4: {count: 1, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "fork"), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 1}, - user4: {count: 2, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "fork"), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 1}, - user4: {count: 2, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "mirror"), expectedResults: expectedResults{ - nil: {count: 2}, - user: {count: 2}, - user4: {count: 4, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "mirror"), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 1}, - user4: {count: 2, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeCollaborative", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "collaborative"), expectedResults: expectedResults{ - nil: {count: 0}, - user: {count: 1, includesPrivate: true}, - user4: {count: 1, includesPrivate: true}, - }}, - } - - repoCache := cache.NewEphemeralCache(time.Hour) - getRepo := func(t *testing.T, id int64) *repo_model.Repository { - r, _ := cache.GetWithEphemeralCache(t.Context(), repoCache, "cache-group-test", fmt.Sprintf("repo:%d", id), func(context.Context, string) (*repo_model.Repository, error) { - return unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: id}), nil - }) - return r - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - for userToLogin, expected := range testCase.expectedResults { - var testName string - var userID int64 - var token string - if userToLogin != nil && userToLogin.ID > 0 { - testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID) - session := loginUser(t, userToLogin.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - userID = userToLogin.ID - } else { - testName = "AnonymousUser" - _ = emptyTestSession(t) - } - - t.Run(testName, func(t *testing.T) { - request := NewRequest(t, "GET", testCase.requestURL). - AddTokenAuth(token) - response := MakeRequest(t, request, http.StatusOK) - - body := DecodeJSON(t, response, &api.SearchResults{}) - - repoNames := make([]string, 0, len(body.Data)) - for _, repo := range body.Data { - repoNames = append(repoNames, fmt.Sprintf("%d:%s:%t", repo.ID, repo.FullName, repo.Private)) - } - assert.Len(t, repoNames, expected.count) - for _, repo := range body.Data { - r := getRepo(t, repo.ID) - hasAccess, err := access_model.HasAnyUnitAccess(t.Context(), userID, r) - assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err) - assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName) - - assert.NotEmpty(t, repo.Name) - assert.Equal(t, repo.Name, r.Name) - - if len(expected.repoName) > 0 { - assert.Contains(t, repo.Name, expected.repoName) - } - - if expected.repoOwnerID > 0 { - assert.Equal(t, expected.repoOwnerID, repo.Owner.ID) - } - - if !expected.includesPrivate { - assert.False(t, repo.Private, "User: %d not expecting private repository: %s", userID, repo.FullName) - } - } - }) - } - }) - } -} - -func TestAPIViewRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") - resp := MakeRequest(t, req, http.StatusOK) - repo := DecodeJSON(t, resp, &api.Repository{}) - assert.EqualValues(t, 1, repo.ID) - assert.Equal(t, "repo1", repo.Name) - assert.Equal(t, 2, repo.Releases) - assert.Equal(t, 1, repo.OpenIssues) - assert.Equal(t, 3, repo.OpenPulls) - - req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10") - resp = MakeRequest(t, req, http.StatusOK) - repo = DecodeJSON(t, resp, &api.Repository{}) - assert.EqualValues(t, 10, repo.ID) - assert.Equal(t, "repo10", repo.Name) - assert.Equal(t, 1, repo.OpenPulls) - assert.Equal(t, 1, repo.Forks) - - req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4") - resp = MakeRequest(t, req, http.StatusOK) - repo = DecodeJSON(t, resp, &api.Repository{}) - assert.EqualValues(t, 4, repo.ID) - assert.Equal(t, "repo4", repo.Name) - assert.Equal(t, 1, repo.Stars) -} - -func TestAPIOrgRepos(t *testing.T) { - defer tests.PrepareTestEnv(t)() - userOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - userAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - userOther := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - - // org3 is an Org. Try to log in as different doers to list its repos. - targetOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - - testCases := []struct { - name string - doer *user_model.User - count int - privateCount int - }{ - {name: "Anonymous", doer: nil, count: 1}, - {name: "UserNormal", doer: userOwner, count: 3, privateCount: 2}, - {name: "UserAdmin", doer: userAdmin, count: 3, privateCount: 2}, - {name: "UserOther", doer: userOther, count: 1}, - } - - for _, tc := range testCases { - var token string - if tc.doer != nil { - session := loginUser(t, tc.doer.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + user4: {count: 7, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeSource", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "source"), expectedResults: expectedResults{ + nil: {count: 0}, + user: {count: 1, includesPrivate: true}, + user4: {count: 1, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "fork"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "fork"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "mirror"), expectedResults: expectedResults{ + nil: {count: 2}, + user: {count: 2}, + user4: {count: 4, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "mirror"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeCollaborative", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "collaborative"), expectedResults: expectedResults{ + nil: {count: 0}, + user: {count: 1, includesPrivate: true}, + user4: {count: 1, includesPrivate: true}, + }}, } - t.Run(tc.name, func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", targetOrg.Name).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + tokens := make(map[int64]string) + repoCache := cache.NewEphemeralCache(time.Hour) + getRepo := func(t *testing.T, id int64) *repo_model.Repository { + r, _ := cache.GetWithEphemeralCache(t.Context(), repoCache, "cache-group-test", fmt.Sprintf("repo:%d", id), func(context.Context, string) (*repo_model.Repository, error) { + return unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: id}), nil + }) + return r + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + for userToLogin, expected := range testCase.expectedResults { + var testName string + var userID int64 + var token string + if userToLogin != nil && userToLogin.ID > 0 { + testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID) + userID = userToLogin.ID + token = tokens[userID] + if token == "" { + token = getTokenForLoggedInUser(t, loginUser(t, userToLogin.Name), auth_model.AccessTokenScopeReadRepository) + tokens[userID] = token + } + } else { + testName = "AnonymousUser" + _ = emptyTestSession(t) + } - apiRepos := DecodeJSON(t, resp, []*api.Repository{}) - assert.Len(t, apiRepos, tc.count) - privateCount := 0 - for _, repo := range apiRepos { - assert.Equal(t, targetOrg.ID, repo.Owner.ID) - if repo.Private { - privateCount++ + t.Run(testName, func(t *testing.T) { + request := NewRequest(t, "GET", testCase.requestURL). + AddTokenAuth(token) + response := MakeRequest(t, request, http.StatusOK) + + body := DecodeJSON(t, response, &api.SearchResults{}) + + repoNames := make([]string, 0, len(body.Data)) + for _, repo := range body.Data { + repoNames = append(repoNames, fmt.Sprintf("%d:%s:%t", repo.ID, repo.FullName, repo.Private)) + } + assert.Len(t, repoNames, expected.count) + for _, repo := range body.Data { + r := getRepo(t, repo.ID) + hasAccess, err := access_model.HasAnyUnitAccess(t.Context(), userID, r) + assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err) + assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName) + + assert.NotEmpty(t, repo.Name) + assert.Equal(t, repo.Name, r.Name) + + if len(expected.repoName) > 0 { + assert.Contains(t, repo.Name, expected.repoName) + } + + if expected.repoOwnerID > 0 { + assert.Equal(t, expected.repoOwnerID, repo.Owner.ID) + } + + if !expected.includesPrivate { + assert.False(t, repo.Private, "User: %d not expecting private repository: %s", userID, repo.FullName) + } + } + }) } + }) + } + }) + + t.Run("ViewRepo", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") + resp := MakeRequest(t, req, http.StatusOK) + repo := DecodeJSON(t, resp, &api.Repository{}) + assert.EqualValues(t, 1, repo.ID) + assert.Equal(t, "repo1", repo.Name) + assert.Equal(t, 2, repo.Releases) + assert.Equal(t, 1, repo.OpenIssues) + assert.Equal(t, 3, repo.OpenPulls) + + req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10") + resp = MakeRequest(t, req, http.StatusOK) + repo = DecodeJSON(t, resp, &api.Repository{}) + assert.EqualValues(t, 10, repo.ID) + assert.Equal(t, "repo10", repo.Name) + assert.Equal(t, 1, repo.OpenPulls) + assert.Equal(t, 1, repo.Forks) + + req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4") + resp = MakeRequest(t, req, http.StatusOK) + repo = DecodeJSON(t, resp, &api.Repository{}) + assert.EqualValues(t, 4, repo.ID) + assert.Equal(t, "repo4", repo.Name) + assert.Equal(t, 1, repo.Stars) + }) + + t.Run("OrgRepos", func(t *testing.T) { + userOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + userAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + userOther := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + + // org3 is an Org. Try to log in as different doers to list its repos. + targetOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + + testCases := []struct { + name string + doer *user_model.User + count int + privateCount int + }{ + {name: "Anonymous", doer: nil, count: 1}, + {name: "UserNormal", doer: userOwner, count: 3, privateCount: 2}, + {name: "UserAdmin", doer: userAdmin, count: 3, privateCount: 2}, + {name: "UserOther", doer: userOther, count: 1}, + } + + for _, tc := range testCases { + var token string + if tc.doer != nil { + session := loginUser(t, tc.doer.Name) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) } - assert.Equal(t, tc.privateCount, privateCount) - }) - } -} -// See issue #28483. Tests to make sure we consider more than just code unit-enabled repositories. -func TestAPIOrgReposWithCodeUnitDisabled(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo21"}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo21.OwnerID}) + t.Run(tc.name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", targetOrg.Name).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - // Disable code repository unit. - var units []unit_model.Type - units = append(units, unit_model.TypeCode) + apiRepos := DecodeJSON(t, resp, []*api.Repository{}) + assert.Len(t, apiRepos, tc.count) + privateCount := 0 + for _, repo := range apiRepos { + assert.Equal(t, targetOrg.ID, repo.Owner.ID) + if repo.Private { + privateCount++ + } + } + assert.Equal(t, tc.privateCount, privateCount) + }) + } + }) - err := repo_service.UpdateRepositoryUnits(t.Context(), repo21, nil, units) - assert.NoError(t, err, "should have been able to delete code repository unit") + t.Run("GetRepoByIDUnauthorized", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + req := NewRequest(t, "GET", "/api/v1/repositories/2"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) - assert.False(t, repo21.UnitEnabled(t.Context(), unit_model.TypeCode)) + // mirror-sync must fail with "400 (Bad Request)" when an attempt is made to + // sync a non-mirror repository. + t.Run("MirrorSyncNonMirrorRepo", func(t *testing.T) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") + resp := MakeRequest(t, req, http.StatusOK) + repo := DecodeJSON(t, resp, &api.Repository{}) + assert.False(t, repo.Mirror) - req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org3.Name). - AddTokenAuth(token) + req = NewRequestf(t, "POST", "/api/v1/repos/user2/repo1/mirror-sync"). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusBadRequest) + errRespJSON := DecodeJSON(t, resp, map[string]string{}) + assert.Equal(t, "Repository is not a mirror", errRespJSON["message"]) + }) - resp := MakeRequest(t, req, http.StatusOK) - apiRepos := DecodeJSON(t, resp, []*api.Repository{}) + t.Run("RepoGetReviewers", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - var repoNames []string - for _, r := range apiRepos { - repoNames = append(repoNames, r.Name) - } + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers", user.Name, repo.Name). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + reviewers := DecodeJSON(t, resp, []*api.User{}) + if assert.Len(t, reviewers, 1) { + assert.ElementsMatch(t, []int64{2}, []int64{reviewers[0].ID}) + } + }) - assert.Contains(t, repoNames, repo21.Name) -} + t.Run("RepoGetAssignees", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) -func TestAPIGetRepoByIDUnauthorized(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequest(t, "GET", "/api/v1/repositories/2"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees", user.Name, repo.Name). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + assignees := DecodeJSON(t, resp, []*api.User{}) + assert.Len(t, assignees, 1) + + assignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, assignee.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + nonAssignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, nonAssignee.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, "org3"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusBadRequest) + }) + + // See issue #28483. Tests to make sure we consider more than just code unit-enabled repositories. + t.Run("OrgReposWithCodeUnitDisabled", func(t *testing.T) { + repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo21"}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo21.OwnerID}) + + // Disable code repository unit. + var units []unit_model.Type + units = append(units, unit_model.TypeCode) + + err := repo_service.UpdateRepositoryUnits(t.Context(), repo21, nil, units) + assert.NoError(t, err, "should have been able to delete code repository unit") + + assert.False(t, repo21.UnitEnabled(t.Context(), unit_model.TypeCode)) + + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org3.Name). + AddTokenAuth(token) + + resp := MakeRequest(t, req, http.StatusOK) + apiRepos := DecodeJSON(t, resp, []*api.Repository{}) + + var repoNames []string + for _, r := range apiRepos { + repoNames = append(repoNames, r.Name) + } + + assert.Contains(t, repoNames, repo21.Name) + }) } func TestAPIRepoMigrate(t *testing.T) { @@ -460,26 +521,6 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { }) } -// mirror-sync must fail with "400 (Bad Request)" when an attempt is made to -// sync a non-mirror repository. -func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") - resp := MakeRequest(t, req, http.StatusOK) - repo := DecodeJSON(t, resp, &api.Repository{}) - assert.False(t, repo.Mirror) - - req = NewRequestf(t, "POST", "/api/v1/repos/user2/repo1/mirror-sync"). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusBadRequest) - errRespJSON := DecodeJSON(t, resp, map[string]string{}) - assert.Equal(t, "Repository is not a mirror", errRespJSON["message"]) -} - func testAPIOrgCreateRepo(t *testing.T, session *TestSession, orgName, repoName string, status int) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) @@ -687,85 +728,28 @@ func TestAPIGenerateRepo(t *testing.T) { templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) - assertGeneratedRepoIsUsable := func(t *testing.T, ownerName string, repo *api.Repository) { - t.Helper() - assert.NotEmpty(t, repo.DefaultBranch) + for _, ownerName := range []string{ + // user + user.Name, + // org + "org3", + } { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + Owner: ownerName, + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + apiRepo := DecodeJSON(t, resp, &api.Repository{}) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s", ownerName, repo.Name, repo.DefaultBranch).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "new-repo", apiRepo.Name) + assert.NotEmpty(t, apiRepo.DefaultBranch) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s", ownerName, apiRepo.Name, apiRepo.DefaultBranch).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) branch := DecodeJSON(t, resp, &api.Branch{}) - assert.Equal(t, repo.DefaultBranch, branch.Name) - } - - // user - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ - Owner: user.Name, - Name: "new-repo", - Description: "test generate repo", - Private: false, - GitContent: true, - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiRepo := DecodeJSON(t, resp, &api.Repository{}) - - assert.Equal(t, "new-repo", apiRepo.Name) - assertGeneratedRepoIsUsable(t, user.Name, apiRepo) - - // org - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ - Owner: "org3", - Name: "new-repo", - Description: "test generate repo", - Private: false, - GitContent: true, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusCreated) - apiRepo = DecodeJSON(t, resp, &api.Repository{}) - - assert.Equal(t, "new-repo", apiRepo.Name) - assertGeneratedRepoIsUsable(t, "org3", apiRepo) -} - -func TestAPIRepoGetReviewers(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers", user.Name, repo.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - reviewers := DecodeJSON(t, resp, []*api.User{}) - if assert.Len(t, reviewers, 1) { - assert.ElementsMatch(t, []int64{2}, []int64{reviewers[0].ID}) + assert.Equal(t, apiRepo.DefaultBranch, branch.Name) } } - -func TestAPIRepoGetAssignees(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees", user.Name, repo.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assignees := DecodeJSON(t, resp, []*api.User{}) - assert.Len(t, assignees, 1) - - assignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, assignee.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - nonAssignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, nonAssignee.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, "org3"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) -} diff --git a/tests/integration/api_repo_topic_test.go b/tests/integration/api_repo_topic_test.go index bd45f0d440d..b1fa976507b 100644 --- a/tests/integration/api_repo_topic_test.go +++ b/tests/integration/api_repo_topic_test.go @@ -19,197 +19,168 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPITopicSearch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - searchURL, _ := url.Parse("/api/v1/topics/search") - - // search all topics - res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics := DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 6) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // pagination search topics first page - query := url.Values{"page": []string{"1"}, "limit": []string{"4"}} - - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 4) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // pagination search topics second page - query = url.Values{"page": []string{"2"}, "limit": []string{"4"}} - - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 2) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // add keyword search - query = url.Values{"page": []string{"1"}, "limit": []string{"4"}} - query.Add("q", "topic") - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 2) - - query.Set("q", "database") - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - if assert.Len(t, topics.TopicNames, 1) { - assert.EqualValues(t, 2, topics.TopicNames[0].ID) - assert.Equal(t, "database", topics.TopicNames[0].Name) - assert.Equal(t, 1, topics.TopicNames[0].RepoCount) - } -} - func TestAPIRepoTopic(t *testing.T) { defer tests.PrepareTestEnv(t)() - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of repo2 - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of repo3 - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // write access to repo 3 - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - // Get user2's token - token2 := getUserToken(t, user2.Name, auth_model.AccessTokenScopeWriteRepository) + t.Run("Search", func(t *testing.T) { + searchURL, _ := url.Parse("/api/v1/topics/search") - // Test read topics using login - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name)). - AddTokenAuth(token2) - res := MakeRequest(t, req, http.StatusOK) - topics := DecodeJSON(t, res, &api.TopicName{}) - assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames) + for _, testCase := range []struct { + query url.Values + count int + total string + }{ + // search all topics + {nil, 6, "6"}, + // pagination search topics first page + {url.Values{"page": {"1"}, "limit": {"4"}}, 4, "6"}, + // pagination search topics second page + {url.Values{"page": {"2"}, "limit": {"4"}}, 2, "6"}, + // add keyword search + {url.Values{"page": {"1"}, "limit": {"4"}, "q": {"topic"}}, 2, ""}, + {url.Values{"page": {"1"}, "limit": {"4"}, "q": {"database"}}, 1, ""}, + } { + searchURL.RawQuery = testCase.query.Encode() + res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + topics := DecodeJSON(t, res, &struct { + TopicNames []*api.TopicResponse `json:"topics"` + }{}) + validLength := assert.Len(t, topics.TopicNames, testCase.count) + if testCase.total != "" { + assert.Equal(t, testCase.total, res.Header().Get("x-total-count")) + } + if testCase.query.Get("q") == "database" && validLength { + assert.EqualValues(t, 2, topics.TopicNames[0].ID) + assert.Equal(t, "database", topics.TopicNames[0].Name) + assert.Equal(t, 1, topics.TopicNames[0].RepoCount) + } + } + }) - // Test delete a topic - req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) + t.Run("Archived", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 30}) // owner of the archived repo51 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 51}) + assert.True(t, repo.IsArchived) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteRepository) - // Test add an existing topic - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Golang"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) + // writing topics on an archived repo must be rejected, matching the web UI + req := NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user.Name, repo.Name, "archivedtopic"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusLocked) - // Test add a topic - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "topicName3"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user.Name, repo.Name, "archivedtopic"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusLocked) - url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user.Name, repo.Name), + &api.RepoTopicOptions{Topics: []string{"archivedtopic"}}).AddTokenAuth(token) + MakeRequest(t, req, http.StatusLocked) - // Test read topics using token - req = NewRequest(t, "GET", url). - AddTokenAuth(token2) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.ElementsMatch(t, []string{"topicname2", "golang", "topicname3"}, topics.TopicNames) + // reading topics stays allowed on an archived repo + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user.Name, repo.Name)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + }) - // Test replace topics - newTopics := []string{" windows ", " ", "MAC "} - req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ - Topics: newTopics, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", url). - AddTokenAuth(token2) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) + t.Run("Repository", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of repo2 + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of repo3 + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // write access to repo 3 + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - // Test replace topics with something invalid - newTopics = []string{"topicname1", "topicname2", "topicname!"} - req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ - Topics: newTopics, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusUnprocessableEntity) - req = NewRequest(t, "GET", url). - AddTokenAuth(token2) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) + // Get user2's token + token2 := getUserToken(t, user2.Name, auth_model.AccessTokenScopeWriteRepository) - // Test with some topics multiple times, less than 25 unique - newTopics = []string{"t1", "t2", "t1", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10", "t11", "t12", "t13", "t14", "t15", "t16", "17", "t18", "t19", "t20", "t21", "t22", "t23", "t24", "t25"} - req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ - Topics: newTopics, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", url). - AddTokenAuth(token2) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.Len(t, topics.TopicNames, 25) + // Test read topics using login + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name)). + AddTokenAuth(token2) + res := MakeRequest(t, req, http.StatusOK) + topics := DecodeJSON(t, res, &api.TopicName{}) + assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames) - // Test writing more topics than allowed - newTopics = append(newTopics, "t26") - req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ - Topics: newTopics, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusUnprocessableEntity) + // Test delete a topic + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusNoContent) - // Test add a topic when there is already maximum - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "t26"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusUnprocessableEntity) + // Test add an existing topic + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Golang"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusNoContent) - // Test delete a topic that repo doesn't have - req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNotFound) + // Test add a topic + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "topicName3"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusNoContent) - // Get user4's token - token4 := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteRepository) + url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) - // Test read topics with write access - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", org3.Name, repo3.Name)). - AddTokenAuth(token4) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.Empty(t, topics.TopicNames) + // Test read topics using token + req = NewRequest(t, "GET", url). + AddTokenAuth(token2) + res = MakeRequest(t, req, http.StatusOK) + topics = DecodeJSON(t, res, &api.TopicName{}) + assert.ElementsMatch(t, []string{"topicname2", "golang", "topicname3"}, topics.TopicNames) - // Test add a topic to repo with write access (requires repo admin access) - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", org3.Name, repo3.Name, "topicName"). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) -} - -func TestAPIRepoTopicArchived(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 30}) // owner of the archived repo51 - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 51}) - assert.True(t, repo.IsArchived) - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteRepository) - - // writing topics on an archived repo must be rejected, matching the web UI - req := NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user.Name, repo.Name, "archivedtopic"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusLocked) - - req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user.Name, repo.Name, "archivedtopic"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusLocked) - - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user.Name, repo.Name), - &api.RepoTopicOptions{Topics: []string{"archivedtopic"}}).AddTokenAuth(token) - MakeRequest(t, req, http.StatusLocked) - - // reading topics stays allowed on an archived repo - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user.Name, repo.Name)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + newTopics := []string{"t1", "t2", "t1", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10", "t11", "t12", "t13", "t14", "t15", "t16", "17", "t18", "t19", "t20", "t21", "t22", "t23", "t24", "t25"} + for _, testCase := range []struct { + topics []string + status int + expected []string + }{ + // Test replace topics + {[]string{" windows ", " ", "MAC "}, http.StatusNoContent, []string{"windows", "mac"}}, + // Test replace topics with something invalid + {[]string{"topicname1", "topicname2", "topicname!"}, http.StatusUnprocessableEntity, []string{"windows", "mac"}}, + // Test with some topics multiple times, less than 25 unique + {newTopics, http.StatusNoContent, nil}, + } { + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: testCase.topics, + }).AddTokenAuth(token2) + MakeRequest(t, req, testCase.status) + req = NewRequest(t, "GET", url). + AddTokenAuth(token2) + res = MakeRequest(t, req, http.StatusOK) + topics = DecodeJSON(t, res, &api.TopicName{}) + if testCase.expected != nil { + assert.ElementsMatch(t, testCase.expected, topics.TopicNames) + } else { + assert.Len(t, topics.TopicNames, 25) + } + } + + // Test writing more topics than allowed + newTopics = append(newTopics, "t26") + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }).AddTokenAuth(token2) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test add a topic when there is already maximum + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "t26"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test delete a topic that repo doesn't have + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusNotFound) + + // Get user4's token + token4 := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteRepository) + + // Test read topics with write access + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", org3.Name, repo3.Name)). + AddTokenAuth(token4) + res = MakeRequest(t, req, http.StatusOK) + topics = DecodeJSON(t, res, &api.TopicName{}) + assert.Empty(t, topics.TopicNames) + + // Test add a topic to repo with write access (requires repo admin access) + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", org3.Name, repo3.Name, "topicName"). + AddTokenAuth(token4) + MakeRequest(t, req, http.StatusForbidden) + }) } diff --git a/tests/integration/api_repository_creation_token_scope_test.go b/tests/integration/api_repository_creation_token_scope_test.go index 7091fd1ef7a..63035a3b44b 100644 --- a/tests/integration/api_repository_creation_token_scope_test.go +++ b/tests/integration/api_repository_creation_token_scope_test.go @@ -20,9 +20,10 @@ func TestAPIRepositoryCreationTokenScopes(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + session := loginUser(t, user.Name) t.Run("migration rejects public-only tokens", func(t *testing.T) { - publicOnlyToken := getUserToken(t, user.Name, + publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopePublicOnly, ) @@ -30,19 +31,19 @@ func TestAPIRepositoryCreationTokenScopes(t *testing.T) { AddTokenAuth(publicOnlyToken) MakeRequest(t, req, http.StatusForbidden) - writeRepoToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteRepository) + writeRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req = NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", &api.MigrateRepoOptions{Private: true}). AddTokenAuth(writeRepoToken) MakeRequest(t, req, http.StatusUnprocessableEntity) }) t.Run("organization creation requires repository scope", func(t *testing.T) { - orgOnlyToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteOrganization) + orgOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/repos", &api.CreateRepoOption{Name: "missing-repository-scope"}). AddTokenAuth(orgOnlyToken) MakeRequest(t, req, http.StatusForbidden) - orgRepoToken := getUserToken(t, user.Name, + orgRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository, ) diff --git a/tests/integration/api_team_test.go b/tests/integration/api_team_test.go index 17a86ede464..be34f2f682e 100644 --- a/tests/integration/api_team_test.go +++ b/tests/integration/api_team_test.go @@ -29,176 +29,309 @@ import ( func TestAPITeam(t *testing.T) { defer tests.PrepareTestEnv(t)() - teamUser := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 1}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamUser.TeamID}) - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: teamUser.OrgID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser.UID}) + t.Run("Search", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) - req := NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + results := DecodeJSON(t, resp, &TeamSearchResults{}) + assert.NotEmpty(t, results.Data) + assert.Len(t, results.Data, 1) + assert.Equal(t, "test_team", results.Data[0].Name) - apiTeam := DecodeJSON(t, resp, &api.Team{}) - assert.Equal(t, team.ID, apiTeam.ID) - assert.Equal(t, team.Name, apiTeam.Name) - assert.Equal(t, convert.ToOrganization(t.Context(), org), apiTeam.Organization) + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) - // non team member user will not access the teams details - teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser2.UID}) + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team"). + AddTokenAuth(token5) + MakeRequest(t, req, http.StatusForbidden) + }) - session = loginUser(t, user2.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusForbidden) + t.Run("GetTeamRepo", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) + teamRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 24}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID) - _ = MakeRequest(t, req, http.StatusUnauthorized) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) + req := NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/", team.ID, teamRepo.FullName()). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &api.Repository{}) + assert.Equal(t, "big_test_private_4", teamRepo.Name) - // Get an admin user able to create, update and delete teams. - user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - session = loginUser(t, user.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) - org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6}) + req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/", team.ID, teamRepo.FullName()). + AddTokenAuth(token5) + MakeRequest(t, req, http.StatusNotFound) + }) - // Create team. - teamToCreate := &api.CreateTeamOption{ - Name: "team1", - Description: "team one", - IncludesAllRepositories: true, - Permission: "read", - Units: []string{"repo.code", "repo.issues"}, - } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", org.Name), teamToCreate). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusCreated) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - expectedTeamUnitsMap := map[string]string{"repo.code": "read", "repo.issues": "read"} - checkTeamResponse(t, "CreateTeam1", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, expectedTeamUnitsMap) - checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, expectedTeamUnitsMap) - teamID := apiTeam.ID + t.Run("CRUD", func(t *testing.T) { + teamUser := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 1}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamUser.TeamID}) + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: teamUser.OrgID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser.UID}) - // Edit team. - editDescription := "team 1" - editFalse := false - teamToEdit := &api.EditTeamOption{ - Name: "teamone", - Description: &editDescription, - Permission: "admin", - IncludesAllRepositories: &editFalse, - } + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + req := NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEdit). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam1", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) - checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) + apiTeam := DecodeJSON(t, resp, &api.Team{}) + assert.Equal(t, team.ID, apiTeam.ID) + assert.Equal(t, team.Name, apiTeam.Name) + assert.Equal(t, convert.ToOrganization(t.Context(), org), apiTeam.Organization) - // Edit team Description only - editDescription = "first team" - teamToEditDesc := api.EditTeamOption{Description: &editDescription} - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEditDesc). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam1_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) - checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) + // non team member user will not access the teams details + teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser2.UID}) - // Edit team to granular permissions, team's permission should be reset to none - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), api.EditTeamOption{ - Permission: "read", - Units: []string{"repo.code", "repo.issues"}, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam1_Granular", apiTeam, teamToEdit.Name, editDescription, editFalse, api.AccessLevelNameNone, expectedTeamUnitsMap) - checkTeamBean(t, teamID, teamToEdit.Name, editDescription, editFalse, api.AccessLevelNameNone, expectedTeamUnitsMap) + session = loginUser(t, user2.Name) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusForbidden) - // Read team. - teamRead := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, teamRead.LoadUnits(t.Context())) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "ReadTeam1", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitsMap()) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID) + _ = MakeRequest(t, req, http.StatusUnauthorized) - // Delete team. - req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + // Get an admin user able to create, update and delete teams. + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session = loginUser(t, user.Name) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - // create team again via UnitsMap (granular: do not send permission=write). - teamToCreate = &api.CreateTeamOption{ - Name: "team2", - Description: "team two", - IncludesAllRepositories: true, - UnitsMap: map[string]string{"repo.code": "read", "repo.issues": "write", "repo.wiki": "none"}, - } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", org.Name), teamToCreate). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusCreated) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "CreateTeam2", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, teamToCreate.UnitsMap) - checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, teamToCreate.UnitsMap) - teamID = apiTeam.ID + org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6}) - // Create a team with permission=write, then edit it to granular permissions later, team's permission should be reset to none - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), api.EditTeamOption{ - Permission: "write", - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam2_Write", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameWrite, nil) - checkTeamBean(t, teamID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameWrite, nil) + // Create team. + teamToCreate := &api.CreateTeamOption{ + Name: "team1", + Description: "team one", + IncludesAllRepositories: true, + Permission: "read", + Units: []string{"repo.code", "repo.issues"}, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", org.Name), teamToCreate). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusCreated) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + expectedTeamUnitsMap := map[string]string{"repo.code": "read", "repo.issues": "read"} + checkTeamResponse(t, "CreateTeam1", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, expectedTeamUnitsMap) + checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, expectedTeamUnitsMap) + teamID := apiTeam.ID - // Edit team. - editDescription = "team 1" - editFalse = false - teamToEdit = &api.EditTeamOption{ - Name: "teamtwo", - Description: &editDescription, - IncludesAllRepositories: &editFalse, - UnitsMap: map[string]string{"repo.code": "read", "repo.pulls": "read", "repo.releases": "write"}, - } + // Edit team. + editDescription := "team 1" + editFalse := false + teamToEdit := &api.EditTeamOption{ + Name: "teamone", + Description: &editDescription, + Permission: "admin", + IncludesAllRepositories: &editFalse, + } - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEdit). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam2", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) - checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEdit). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam1", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) - // Edit team Description only - editDescription = "second team" - teamToEditDesc = api.EditTeamOption{Description: &editDescription} - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEditDesc). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam2_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) - checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + // Edit team Description only + editDescription = "first team" + teamToEditDesc := api.EditTeamOption{Description: &editDescription} + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEditDesc). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam1_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) - // Read team. - teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - assert.NoError(t, teamRead.LoadUnits(t.Context())) - checkTeamResponse(t, "ReadTeam2", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitsMap()) + // Edit team to granular permissions, team's permission should be reset to none + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), api.EditTeamOption{ + Permission: "read", + Units: []string{"repo.code", "repo.issues"}, + }).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam1_Granular", apiTeam, teamToEdit.Name, editDescription, editFalse, api.AccessLevelNameNone, expectedTeamUnitsMap) + checkTeamBean(t, teamID, teamToEdit.Name, editDescription, editFalse, api.AccessLevelNameNone, expectedTeamUnitsMap) - // Delete team. - req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + // Read team. + teamRead := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + assert.NoError(t, teamRead.LoadUnits(t.Context())) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "ReadTeam1", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitsMap()) + + // Delete team. + req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + + // create team again via UnitsMap (granular: do not send permission=write). + teamToCreate = &api.CreateTeamOption{ + Name: "team2", + Description: "team two", + IncludesAllRepositories: true, + UnitsMap: map[string]string{"repo.code": "read", "repo.issues": "write", "repo.wiki": "none"}, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", org.Name), teamToCreate). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusCreated) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "CreateTeam2", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, teamToCreate.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, teamToCreate.UnitsMap) + teamID = apiTeam.ID + + // Create a team with permission=write, then edit it to granular permissions later, team's permission should be reset to none + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), api.EditTeamOption{ + Permission: "write", + }).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam2_Write", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameWrite, nil) + checkTeamBean(t, teamID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameWrite, nil) + + // Edit team. + editDescription = "team 1" + editFalse = false + teamToEdit = &api.EditTeamOption{ + Name: "teamtwo", + Description: &editDescription, + IncludesAllRepositories: &editFalse, + UnitsMap: map[string]string{"repo.code": "read", "repo.pulls": "read", "repo.releases": "write"}, + } + + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEdit). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam2", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + + // Edit team Description only + editDescription = "second team" + teamToEditDesc = api.EditTeamOption{Description: &editDescription} + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEditDesc). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam2_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + + // Read team. + teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + assert.NoError(t, teamRead.LoadUnits(t.Context())) + checkTeamResponse(t, "ReadTeam2", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitsMap()) + + // Delete team. + req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + }) + + t.Run("VisibilityAccess", func(t *testing.T) { + insertTestTeam := func(t *testing.T, orgID int64, name string, visibility structs.VisibleType) *organization.Team { + t.Helper() + team := &organization.Team{ + OrgID: orgID, + LowerName: name, + Name: name, + AccessMode: perm.AccessModeRead, + Visibility: visibility, + } + assert.NoError(t, db.Insert(t.Context(), team)) + return team + } + + limitedTeam := insertTestTeam(t, 3, "limited-team", structs.VisibleTypeLimited) + + // Org member who can read a limited team must not mutate its repos without membership. + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + token := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequestf(t, "PUT", "/api/v1/teams/%d/repos/org3/repo3", limitedTeam.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + publicTeam := insertTestTeam(t, 23, "public-team", structs.VisibleTypePublic) + + // Public team in a private org must not be readable by outsiders. + outsider := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + token = getUserToken(t, outsider.Name, auth_model.AccessTokenScopeReadOrganization) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", publicTeam.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + + // Member lookup must require org membership even for public teams. + req = NewRequestf(t, "GET", "/api/v1/teams/%d/members/%s", publicTeam.ID, outsider.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + + // A limited team's repo list must not leak repos the viewer cannot access. + // repo3 is private; user28 is an org3 member (team12, no repo access) who can + // read the limited team but has no access to repo3. + assert.NoError(t, db.Insert(t.Context(), &organization.TeamRepo{OrgID: 3, TeamID: limitedTeam.ID, RepoID: 3})) + user28 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) + token28 := getUserToken(t, user28.Name, auth_model.AccessTokenScopeReadOrganization) + + req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos", limitedTeam.ID).AddTokenAuth(token28) + resp := MakeRequest(t, req, http.StatusOK) + var repos []*api.Repository + DecodeJSON(t, resp, &repos) + for _, r := range repos { + assert.NotEqual(t, int64(3), r.ID, "must not leak inaccessible private repo3") + } + + // The single-repo lookup must not confirm an inaccessible repo's existence. + req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/org3/repo3", limitedTeam.ID).AddTokenAuth(token28) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("RepositoryAccess", func(t *testing.T) { + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 12}) + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: team.OrgID}) + assert.False(t, org.RepoAdminChangeTeamAccess) + targetRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 32, OwnerID: org.ID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user, perm.AccessModeAdmin)) + + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteOrganization) + url := fmt.Sprintf("/api/v1/teams/%d/repos/%s", team.ID, targetRepo.FullName()) + + req := NewRequest(t, "PUT", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) + + assert.NoError(t, db.Insert(t.Context(), &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: targetRepo.ID})) + + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) + + siteAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + token = getUserToken(t, siteAdmin.Name, auth_model.AccessTokenScopeWriteOrganization) + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) + req = NewRequest(t, "PUT", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) + }) } func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission api.AccessLevelName, unitsMap map[string]string) { @@ -224,141 +357,3 @@ type TeamSearchResults struct { OK bool `json:"ok"` Data []*api.Team `json:"data"` } - -func TestAPITeamSearch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) - req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - results := DecodeJSON(t, resp, &TeamSearchResults{}) - assert.NotEmpty(t, results.Data) - assert.Len(t, results.Data, 1) - assert.Equal(t, "test_team", results.Data[0].Name) - - // no access if not organization member - user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) - - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team"). - AddTokenAuth(token5) - MakeRequest(t, req, http.StatusForbidden) -} - -func TestAPIGetTeamRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) - teamRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 24}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) - req := NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/", team.ID, teamRepo.FullName()). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &api.Repository{}) - assert.Equal(t, "big_test_private_4", teamRepo.Name) - - // no access if not organization member - user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) - - req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/", team.ID, teamRepo.FullName()). - AddTokenAuth(token5) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestAPIAddRemoveTeamRepositoryRequiresOrgOwnerOrSetting(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 12}) - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: team.OrgID}) - assert.False(t, org.RepoAdminChangeTeamAccess) - targetRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 32, OwnerID: org.ID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user, perm.AccessModeAdmin)) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteOrganization) - url := fmt.Sprintf("/api/v1/teams/%d/repos/%s", team.ID, targetRepo.FullName()) - - req := NewRequest(t, "PUT", url).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) - - assert.NoError(t, db.Insert(t.Context(), &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: targetRepo.ID})) - - req = NewRequest(t, "DELETE", url).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) - - siteAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - token = getUserToken(t, siteAdmin.Name, auth_model.AccessTokenScopeWriteOrganization) - req = NewRequest(t, "DELETE", url).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) - req = NewRequest(t, "PUT", url).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) -} - -func TestAPITeamVisibilityAccess(t *testing.T) { - defer tests.PrepareTestEnv(t)() - insertTestTeam := func(t *testing.T, orgID int64, name string, visibility structs.VisibleType) *organization.Team { - t.Helper() - team := &organization.Team{ - OrgID: orgID, - LowerName: name, - Name: name, - AccessMode: perm.AccessModeRead, - Visibility: visibility, - } - assert.NoError(t, db.Insert(t.Context(), team)) - return team - } - - limitedTeam := insertTestTeam(t, 3, "limited-team", structs.VisibleTypeLimited) - - // Org member who can read a limited team must not mutate its repos without membership. - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - token := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteOrganization) - req := NewRequestf(t, "PUT", "/api/v1/teams/%d/repos/org3/repo3", limitedTeam.ID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - publicTeam := insertTestTeam(t, 23, "public-team", structs.VisibleTypePublic) - - // Public team in a private org must not be readable by outsiders. - outsider := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token = getUserToken(t, outsider.Name, auth_model.AccessTokenScopeReadOrganization) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", publicTeam.ID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - // Member lookup must require org membership even for public teams. - req = NewRequestf(t, "GET", "/api/v1/teams/%d/members/%s", publicTeam.ID, outsider.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - // A limited team's repo list must not leak repos the viewer cannot access. - // repo3 is private; user28 is an org3 member (team12, no repo access) who can - // read the limited team but has no access to repo3. - assert.NoError(t, db.Insert(t.Context(), &organization.TeamRepo{OrgID: 3, TeamID: limitedTeam.ID, RepoID: 3})) - user28 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - token28 := getUserToken(t, user28.Name, auth_model.AccessTokenScopeReadOrganization) - - req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos", limitedTeam.ID).AddTokenAuth(token28) - resp := MakeRequest(t, req, http.StatusOK) - var repos []*api.Repository - DecodeJSON(t, resp, &repos) - for _, r := range repos { - assert.NotEqual(t, int64(3), r.ID, "must not leak inaccessible private repo3") - } - - // The single-repo lookup must not confirm an inaccessible repo's existence. - req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/org3/repo3", limitedTeam.ID).AddTokenAuth(token28) - MakeRequest(t, req, http.StatusNotFound) -} diff --git a/tests/integration/api_token_self_test.go b/tests/integration/api_token_self_test.go index 2720c59d2c5..8f6841e76b3 100644 --- a/tests/integration/api_token_self_test.go +++ b/tests/integration/api_token_self_test.go @@ -20,35 +20,28 @@ import ( func TestAPIGetCurrentToken(t *testing.T) { defer tests.PrepareTestEnv(t)() - t.Run("Success with all scopes", func(t *testing.T) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-get-current-token-all", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + for _, testCase := range []struct { + name, tokenName string + scope auth_model.AccessTokenScope + }{ + {"Success with all scopes", "test-get-current-token-all", auth_model.AccessTokenScopeAll}, + {"Success with limited scopes", "test-get-current-token-limited", auth_model.AccessTokenScopeReadRepository}, + } { + t.Run(testCase.name, func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + accessToken := createAPIAccessTokenWithoutCleanUp(t, testCase.tokenName, user, []auth_model.AccessTokenScope{testCase.scope}) - req := NewRequest(t, "GET", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequest(t, "GET", "/api/v1/token"). + AddTokenAuth(accessToken.Token) + resp := MakeRequest(t, req, http.StatusOK) - currentToken := DecodeJSON(t, resp, &api.CurrentAccessToken{}) - assert.Equal(t, accessToken.ID, currentToken.ID) - assert.Equal(t, accessToken.Name, currentToken.Name) - assert.Equal(t, user.ID, currentToken.User.ID) - assert.Equal(t, user.Name, currentToken.User.Login) - }) - - t.Run("Success with limited scopes", func(t *testing.T) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-get-current-token-limited", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeReadRepository}) - - req := NewRequest(t, "GET", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - resp := MakeRequest(t, req, http.StatusOK) - - currentToken := DecodeJSON(t, resp, &api.CurrentAccessToken{}) - assert.Equal(t, accessToken.ID, currentToken.ID) - assert.Equal(t, accessToken.Name, currentToken.Name) - assert.Equal(t, user.ID, currentToken.User.ID) - assert.Equal(t, user.Name, currentToken.User.Login) - }) + currentToken := DecodeJSON(t, resp, &api.CurrentAccessToken{}) + assert.Equal(t, accessToken.ID, currentToken.ID) + assert.Equal(t, accessToken.Name, currentToken.Name) + assert.Equal(t, user.ID, currentToken.User.ID) + assert.Equal(t, user.Name, currentToken.User.Login) + }) + } t.Run("Bad token", func(t *testing.T) { req := NewRequest(t, "GET", "/api/v1/token"). @@ -58,41 +51,39 @@ func TestAPIGetCurrentToken(t *testing.T) { req = NewRequest(t, "GET", "/api/v1/token") MakeRequest(t, req, http.StatusUnauthorized) }) -} -// TestAPITokenSelfService tests delete operations on token -func TestAPITokenSelfService(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // TestAPITokenSelfService tests delete operations on token + t.Run("SelfService", func(t *testing.T) { + t.Run("Success then verify deleted", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-delete-current-token", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - t.Run("Success then verify deleted", func(t *testing.T) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-delete-current-token", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + // Delete the token via the endpoint + req := NewRequest(t, "DELETE", "/api/v1/token"). + AddTokenAuth(accessToken.Token) + MakeRequest(t, req, http.StatusNoContent) - // Delete the token via the endpoint - req := NewRequest(t, "DELETE", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - MakeRequest(t, req, http.StatusNoContent) + // Verify the token is deleted + unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: accessToken.ID}) - // Verify the token is deleted - unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: accessToken.ID}) + // Verify the token can no longer be used for GET + req = NewRequest(t, "GET", "/api/v1/token"). + AddTokenAuth(accessToken.Token) + MakeRequest(t, req, http.StatusUnauthorized) - // Verify the token can no longer be used for GET - req = NewRequest(t, "GET", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - MakeRequest(t, req, http.StatusUnauthorized) + // Verify the token can no longer be used for DELETE + req = NewRequest(t, "DELETE", "/api/v1/token"). + AddTokenAuth(accessToken.Token) + MakeRequest(t, req, http.StatusUnauthorized) + }) - // Verify the token can no longer be used for DELETE - req = NewRequest(t, "DELETE", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - MakeRequest(t, req, http.StatusUnauthorized) - }) + t.Run("Bad token", func(t *testing.T) { + req := NewRequest(t, "DELETE", "/api/v1/token"). + AddTokenAuth("this does not exist") + MakeRequest(t, req, http.StatusUnauthorized) - t.Run("Bad token", func(t *testing.T) { - req := NewRequest(t, "DELETE", "/api/v1/token"). - AddTokenAuth("this does not exist") - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", "/api/v1/token") - MakeRequest(t, req, http.StatusUnauthorized) + req = NewRequest(t, "DELETE", "/api/v1/token") + MakeRequest(t, req, http.StatusUnauthorized) + }) }) } diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go index 7f2986b1a2f..566a2de94e6 100644 --- a/tests/integration/api_token_test.go +++ b/tests/integration/api_token_test.go @@ -18,132 +18,243 @@ import ( "github.com/stretchr/testify/assert" ) -// TestAPICreateAndDeleteToken tests that token that was just created can be deleted -func TestAPICreateAndDeleteToken(t *testing.T) { +func TestAPITokens(t *testing.T) { defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - deleteAPIAccessToken(t, newAccessToken, user) + // TestAPIGetTokensPermission ensures that only the admin can get tokens from other users + t.Run("GetTokensPermission", func(t *testing.T) { + // admin can get tokens for other users + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + req := NewRequest(t, "GET", "/api/v1/users/user2/tokens"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) - newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - deleteAPIAccessToken(t, newAccessToken, user) -} + // non-admin can get tokens for himself + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) -// TestAPICreateTokenScopeEscalation ensures a token-authenticated request cannot -// mint a new token with a broader scope than the authenticating token. -func TestAPICreateTokenScopeEscalation(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - // a write:user-scoped token authenticates the create requests below - writeUserToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteUser) - - // requesting a broader scope ("all") than the authenticating token is rejected - req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "escalated", - "scopes": []string{"all"}, + // non-admin can't get tokens for other users + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusForbidden) }) - req.Request.SetBasicAuth(user.Name, writeUserToken) - MakeRequest(t, req, http.StatusForbidden) - // requesting a subset scope ("read:user") is allowed - req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "subset", - "scopes": []string{"read:user"}, + // TestAPIDeleteMissingToken ensures that error is thrown when token not found + t.Run("DeleteMissingToken", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", unittest.NonexistentID). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNotFound) }) - req.Request.SetBasicAuth(user.Name, writeUserToken) - MakeRequest(t, req, http.StatusCreated) - // password (non-token) auth may still create a token with any scope - req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "by-password", - "scopes": []string{"all"}, - }).AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + // TestAPICreateAndDeleteToken tests that token that was just created can be deleted + t.Run("CreateAndDeleteToken", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - // a public-only token must not mint a token that drops the public-only restriction - publicOnlyToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopePublicOnly) - req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "still-public-only", - "scopes": []string{"write:user"}, + newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + deleteAPIAccessToken(t, newAccessToken, user) + + newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + deleteAPIAccessToken(t, newAccessToken, user) }) - req.Request.SetBasicAuth(user.Name, publicOnlyToken) - resp := MakeRequest(t, req, http.StatusCreated) - var createdToken api.AccessToken - DecodeJSON(t, resp, &createdToken) - assert.Contains(t, createdToken.Scopes, string(auth_model.AccessTokenScopePublicOnly)) - // an unrestricted parent token may create a narrower public-only child: public-only is a restriction, - // not a grantable permission, so the subset check must not reject it - req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "narrower-public-only", - "scopes": []string{"write:user", "public-only"}, + // TestAPIDeniesPermissionBasedOnTokenScope tests that API routes forbid access + // when the correct token scope is not included. + t.Run("DeniesPermissionBasedOnTokenScope", func(t *testing.T) { + // We'll assert that each endpoint, when fetched with a token with all + // scopes *except* the ones specified, a forbidden status code is returned. + // + // This is to protect against endpoints having their access check copied + // from other endpoints and not updated. + // + // Test cases are in alphabetical order by URL. + type route struct { + url, method string + level auth_model.AccessTokenScopeLevel + } + testCases := []struct { + categories []auth_model.AccessTokenScopeCategory + routes []route + }{ + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryAdmin}, + []route{ + {"/api/v1/admin/emails", "GET", auth_model.Read}, + {"/api/v1/admin/users", "GET", auth_model.Read}, + {"/api/v1/admin/users", "POST", auth_model.Write}, + {"/api/v1/admin/users/user2", "PATCH", auth_model.Write}, + {"/api/v1/admin/users/user2/orgs", "GET", auth_model.Read}, + {"/api/v1/admin/users/user2/orgs", "POST", auth_model.Write}, + {"/api/v1/admin/orgs", "GET", auth_model.Read}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryNotification}, + []route{ + {"/api/v1/notifications", "GET", auth_model.Read}, + {"/api/v1/notifications", "PUT", auth_model.Write}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository}, + []route{ + {"/api/v1/org/org1/repos", "POST", auth_model.Write}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryPackage}, + []route{ + {"/api/v1/packages/user1/type/name/1", "GET", auth_model.Read}, + {"/api/v1/packages/user1/type/name/1", "DELETE", auth_model.Write}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryRepository}, + []route{ + {"/api/v1/repos/user1/repo1", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1", "PATCH", auth_model.Write}, + {"/api/v1/repos/user1/repo1", "DELETE", auth_model.Write}, + {"/api/v1/repos/user1/repo1/branches", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1/archive/foo", "GET", auth_model.Read}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryIssue}, + []route{ + {"/api/v1/repos/user1/repo1/issues", "GET", auth_model.Read}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryRepository}, + []route{ + {"/api/v1/repos/user1/repo1/media/foo", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1/raw/foo", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1/teams", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1/teams/team1", "PUT", auth_model.Write}, + {"/api/v1/repos/user1/repo1/transfer", "POST", auth_model.Write}, + // Private repo + {"/api/v1/repos/user2/repo2", "GET", auth_model.Read}, + // Private repo + {"/api/v1/repos/user2/repo2", "GET", auth_model.Read}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryUser}, + []route{ + {"/api/v1/user", "GET", auth_model.Read}, + {"/api/v1/user/emails", "GET", auth_model.Read}, + {"/api/v1/user/emails", "POST", auth_model.Write}, + {"/api/v1/user/emails", "DELETE", auth_model.Write}, + {"/api/v1/user/applications/oauth2", "GET", auth_model.Read}, + {"/api/v1/user/applications/oauth2", "POST", auth_model.Write}, + {"/api/v1/users/search", "GET", auth_model.Read}, + // Private user + {"/api/v1/users/user31", "GET", auth_model.Read}, + // Private user + {"/api/v1/users/user31/gpg_keys", "GET", auth_model.Read}, + }, + }, + } + + // User needs to be admin so that we can verify that tokens without admin + // scopes correctly deny access. + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.True(t, user.IsAdmin, "User needs to be admin") + + for _, testCase := range testCases { + for _, route := range testCase.routes { + requiredPermissions := make([]permission, len(testCase.categories)) + for i, category := range testCase.categories { + requiredPermissions[i] = permission{category, route.level} + } + runTestCase(t, &requiredScopeTestCase{route.url, route.method, requiredPermissions}, user) + } + } }) - req.Request.SetBasicAuth(user.Name, writeUserToken) - resp = MakeRequest(t, req, http.StatusCreated) - DecodeJSON(t, resp, &createdToken) - assert.Contains(t, createdToken.Scopes, string(auth_model.AccessTokenScopePublicOnly)) -} -// TestAPIDeleteMissingToken ensures that error is thrown when token not found -func TestAPIDeleteMissingToken(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + // TestAPIDeleteTokensPermission ensures that only the admin can delete tokens from other users + t.Run("DeleteTokensPermission", func(t *testing.T) { + admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", unittest.NonexistentID). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusNotFound) -} + // admin can delete tokens for other users + createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + req := NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1"). + AddBasicAuth(admin.Name) + MakeRequest(t, req, http.StatusNoContent) -// TestAPIGetTokensPermission ensures that only the admin can get tokens from other users -func TestAPIGetTokensPermission(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // non-admin can delete tokens for himself + createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2"). + AddBasicAuth(user2.Name) + MakeRequest(t, req, http.StatusNoContent) - // admin can get tokens for other users - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - req := NewRequest(t, "GET", "/api/v1/users/user2/tokens"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + // non-admin can't delete tokens for other users + createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3"). + AddBasicAuth(user4.Name) + MakeRequest(t, req, http.StatusForbidden) + }) - // non-admin can get tokens for himself - user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + // TestAPICreateTokenScopeEscalation ensures a token-authenticated request cannot + // mint a new token with a broader scope than the authenticating token. + t.Run("CreateTokenScopeEscalation", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // non-admin can't get tokens for other users - user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusForbidden) -} + // a write:user-scoped token authenticates the create requests below + writeUserToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteUser) -// TestAPIDeleteTokensPermission ensures that only the admin can delete tokens from other users -func TestAPIDeleteTokensPermission(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // requesting a broader scope ("all") than the authenticating token is rejected + req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "escalated", + "scopes": []string{"all"}, + }) + req.Request.SetBasicAuth(user.Name, writeUserToken) + MakeRequest(t, req, http.StatusForbidden) - admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + // requesting a subset scope ("read:user") is allowed + req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "subset", + "scopes": []string{"read:user"}, + }) + req.Request.SetBasicAuth(user.Name, writeUserToken) + MakeRequest(t, req, http.StatusCreated) - // admin can delete tokens for other users - createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - req := NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1"). - AddBasicAuth(admin.Name) - MakeRequest(t, req, http.StatusNoContent) + // password (non-token) auth may still create a token with any scope + req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "by-password", + "scopes": []string{"all"}, + }).AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) - // non-admin can delete tokens for himself - createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2"). - AddBasicAuth(user2.Name) - MakeRequest(t, req, http.StatusNoContent) + // a public-only token must not mint a token that drops the public-only restriction + publicOnlyToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopePublicOnly) + req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "still-public-only", + "scopes": []string{"write:user"}, + }) + req.Request.SetBasicAuth(user.Name, publicOnlyToken) + resp := MakeRequest(t, req, http.StatusCreated) + var createdToken api.AccessToken + DecodeJSON(t, resp, &createdToken) + assert.Contains(t, createdToken.Scopes, string(auth_model.AccessTokenScopePublicOnly)) - // non-admin can't delete tokens for other users - createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3"). - AddBasicAuth(user4.Name) - MakeRequest(t, req, http.StatusForbidden) + // an unrestricted parent token may create a narrower public-only child: public-only is a restriction, + // not a grantable permission, so the subset check must not reject it + req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "narrower-public-only", + "scopes": []string{"write:user", "public-only"}, + }) + req.Request.SetBasicAuth(user.Name, writeUserToken) + resp = MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, &createdToken) + assert.Contains(t, createdToken.Scopes, string(auth_model.AccessTokenScopePublicOnly)) + }) } type permission struct { @@ -161,379 +272,6 @@ func (c *requiredScopeTestCase) Name() string { return fmt.Sprintf("%v %v", c.method, c.url) } -// TestAPIDeniesPermissionBasedOnTokenScope tests that API routes forbid access -// when the correct token scope is not included. -func TestAPIDeniesPermissionBasedOnTokenScope(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // We'll assert that each endpoint, when fetched with a token with all - // scopes *except* the ones specified, a forbidden status code is returned. - // - // This is to protect against endpoints having their access check copied - // from other endpoints and not updated. - // - // Test cases are in alphabetical order by URL. - testCases := []requiredScopeTestCase{ - { - "/api/v1/admin/emails", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Read, - }, - }, - }, - { - "/api/v1/admin/users", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Read, - }, - }, - }, - { - "/api/v1/admin/users", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Write, - }, - }, - }, - { - "/api/v1/admin/users/user2", - "PATCH", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Write, - }, - }, - }, - { - "/api/v1/admin/users/user2/orgs", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Read, - }, - }, - }, - { - "/api/v1/admin/users/user2/orgs", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Write, - }, - }, - }, - { - "/api/v1/admin/orgs", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Read, - }, - }, - }, - { - "/api/v1/notifications", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryNotification, - auth_model.Read, - }, - }, - }, - { - "/api/v1/notifications", - "PUT", - []permission{ - { - auth_model.AccessTokenScopeCategoryNotification, - auth_model.Write, - }, - }, - }, - { - "/api/v1/org/org1/repos", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryOrganization, - auth_model.Write, - }, - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - { - "/api/v1/packages/user1/type/name/1", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryPackage, - auth_model.Read, - }, - }, - }, - { - "/api/v1/packages/user1/type/name/1", - "DELETE", - []permission{ - { - auth_model.AccessTokenScopeCategoryPackage, - auth_model.Write, - }, - }, - }, - { - "/api/v1/repos/user1/repo1", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1", - "PATCH", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - { - "/api/v1/repos/user1/repo1", - "DELETE", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/branches", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/archive/foo", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/issues", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryIssue, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/media/foo", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/raw/foo", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/teams", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/teams/team1", - "PUT", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/transfer", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - // Private repo - { - "/api/v1/repos/user2/repo2", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - // Private repo - { - "/api/v1/repos/user2/repo2", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/user", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - { - "/api/v1/user/emails", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - { - "/api/v1/user/emails", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Write, - }, - }, - }, - { - "/api/v1/user/emails", - "DELETE", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Write, - }, - }, - }, - { - "/api/v1/user/applications/oauth2", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - { - "/api/v1/user/applications/oauth2", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Write, - }, - }, - }, - { - "/api/v1/users/search", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - // Private user - { - "/api/v1/users/user31", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - // Private user - { - "/api/v1/users/user31/gpg_keys", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - } - - // User needs to be admin so that we can verify that tokens without admin - // scopes correctly deny access. - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.True(t, user.IsAdmin, "User needs to be admin") - - for _, testCase := range testCases { - runTestCase(t, &testCase, user) - } -} - // runTestCase Helper function to run a single test case. func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model.User) { t.Run(testCase.Name(), func(t *testing.T) { diff --git a/tests/integration/api_twofa_test.go b/tests/integration/api_twofa_test.go index 141b7e4cdcf..264e04c9240 100644 --- a/tests/integration/api_twofa_test.go +++ b/tests/integration/api_twofa_test.go @@ -20,92 +20,92 @@ import ( func TestAPITwoFactor(t *testing.T) { defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) + t.Run("WebAuthn", func(t *testing.T) { + // user1 has no webauthn enrolled, he can request API with basic auth + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + unittest.AssertNotExistsBean(t, &auth_model.WebAuthnCredential{UserID: user1.ID}) + req := NewRequest(t, "GET", "/api/v1/user") + req.SetBasicAuth(user1.Name, "password") + MakeRequest(t, req, http.StatusOK) - req := NewRequest(t, "GET", "/api/v1/user"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + // user1 has no webauthn enrolled, he can request git protocol with basic auth + req = NewRequest(t, "GET", "/user2/repo1/info/refs") + req.SetBasicAuth(user1.Name, "password") + MakeRequest(t, req, http.StatusOK) - otpKey, err := totp.Generate(totp.GenerateOpts{ - SecretSize: 40, - Issuer: "gitea-test", - AccountName: user.Name, + // user1 has no webauthn enrolled, he can request container package with basic auth + req = NewRequest(t, "GET", "/v2/token") + req.SetBasicAuth(user1.Name, "password") + resp := MakeRequest(t, req, http.StatusOK) + + type tokenResponse struct { + Token string `json:"token"` + } + tokenParsed := DecodeJSON(t, resp, &tokenResponse{}) + assert.NotEmpty(t, tokenParsed.Token) + + // user32 has webauthn enrolled, he can't request API with basic auth + user32 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) + unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{UserID: user32.ID}) + + req = NewRequest(t, "GET", "/api/v1/user") + req.SetBasicAuth(user32.Name, "notpassword") + resp = MakeRequest(t, req, http.StatusUnauthorized) + + type userResponse struct { + Message string `json:"message"` + } + userParsed := DecodeJSON(t, resp, &userResponse{}) + assert.Equal(t, "basic authorization is not allowed while WebAuthn enrolled", userParsed.Message) + + // user32 has webauthn enrolled, he can't request git protocol with basic auth + req = NewRequest(t, "GET", "/user2/repo1/info/refs") + req.SetBasicAuth(user32.Name, "notpassword") + MakeRequest(t, req, http.StatusUnauthorized) + + // user32 has webauthn enrolled, he can't request container package with basic auth + req = NewRequest(t, "GET", "/v2/token") + req.SetBasicAuth(user1.Name, "notpassword") + MakeRequest(t, req, http.StatusUnauthorized) }) - assert.NoError(t, err) - tfa := &auth_model.TwoFactor{ - UID: user.ID, - } - assert.NoError(t, tfa.SetSecret(otpKey.Secret())) + t.Run("TOTP", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) - assert.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) + req := NewRequest(t, "GET", "/api/v1/user"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", "/api/v1/user"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusUnauthorized) + otpKey, err := totp.Generate(totp.GenerateOpts{ + SecretSize: 40, + Issuer: "gitea-test", + AccountName: user.Name, + }) + assert.NoError(t, err) - passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) - assert.NoError(t, err) + tfa := &auth_model.TwoFactor{ + UID: user.ID, + } + assert.NoError(t, tfa.SetSecret(otpKey.Secret())) - req = NewRequest(t, "GET", "/api/v1/user"). - AddBasicAuth(user.Name) - req.Header.Set("X-Gitea-OTP", passcode) - MakeRequest(t, req, http.StatusOK) + assert.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) - // the same passcode must not be replayable on the basic-auth surface (RFC 6238 single-use) - req = NewRequest(t, "GET", "/api/v1/user"). - AddBasicAuth(user.Name) - req.Header.Set("X-Gitea-OTP", passcode) - MakeRequest(t, req, http.StatusUnauthorized) -} - -func TestBasicAuthWithWebAuthn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user1 has no webauthn enrolled, he can request API with basic auth - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - unittest.AssertNotExistsBean(t, &auth_model.WebAuthnCredential{UserID: user1.ID}) - req := NewRequest(t, "GET", "/api/v1/user") - req.SetBasicAuth(user1.Name, "password") - MakeRequest(t, req, http.StatusOK) - - // user1 has no webauthn enrolled, he can request git protocol with basic auth - req = NewRequest(t, "GET", "/user2/repo1/info/refs") - req.SetBasicAuth(user1.Name, "password") - MakeRequest(t, req, http.StatusOK) - - // user1 has no webauthn enrolled, he can request container package with basic auth - req = NewRequest(t, "GET", "/v2/token") - req.SetBasicAuth(user1.Name, "password") - resp := MakeRequest(t, req, http.StatusOK) - - type tokenResponse struct { - Token string `json:"token"` - } - tokenParsed := DecodeJSON(t, resp, &tokenResponse{}) - assert.NotEmpty(t, tokenParsed.Token) - - // user32 has webauthn enrolled, he can't request API with basic auth - user32 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) - unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{UserID: user32.ID}) - - req = NewRequest(t, "GET", "/api/v1/user") - req.SetBasicAuth(user32.Name, "notpassword") - resp = MakeRequest(t, req, http.StatusUnauthorized) - - type userResponse struct { - Message string `json:"message"` - } - userParsed := DecodeJSON(t, resp, &userResponse{}) - assert.Equal(t, "basic authorization is not allowed while WebAuthn enrolled", userParsed.Message) - - // user32 has webauthn enrolled, he can't request git protocol with basic auth - req = NewRequest(t, "GET", "/user2/repo1/info/refs") - req.SetBasicAuth(user32.Name, "notpassword") - MakeRequest(t, req, http.StatusUnauthorized) - - // user32 has webauthn enrolled, he can't request container package with basic auth - req = NewRequest(t, "GET", "/v2/token") - req.SetBasicAuth(user1.Name, "notpassword") - MakeRequest(t, req, http.StatusUnauthorized) + req = NewRequest(t, "GET", "/api/v1/user"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusUnauthorized) + + passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) + assert.NoError(t, err) + + req = NewRequest(t, "GET", "/api/v1/user"). + AddBasicAuth(user.Name) + req.Header.Set("X-Gitea-OTP", passcode) + MakeRequest(t, req, http.StatusOK) + + // the same passcode must not be replayable on the basic-auth surface (RFC 6238 single-use) + req = NewRequest(t, "GET", "/api/v1/user"). + AddBasicAuth(user.Name) + req.Header.Set("X-Gitea-OTP", passcode) + MakeRequest(t, req, http.StatusUnauthorized) + }) } diff --git a/tests/integration/api_user_avatar_test.go b/tests/integration/api_user_avatar_test.go index accf0b7dc7c..d78114610bb 100644 --- a/tests/integration/api_user_avatar_test.go +++ b/tests/integration/api_user_avatar_test.go @@ -25,48 +25,34 @@ func TestAPIUpdateUserAvatar(t *testing.T) { session := loginUser(t, normalUsername) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - // Test what happens if you use a valid image - avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) - assert.NoError(t, err) - if err != nil { - assert.FailNow(t, "Unable to open avatar.png") - } + t.Run("Delete", func(t *testing.T) { + req := NewRequest(t, "DELETE", "/api/v1/user/avatar"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) - // Test what happens if you don't have a valid Base64 string - opts := api.UpdateUserAvatarOption{ - Image: base64.StdEncoding.EncodeToString(avatar), - } + t.Run("Update", func(t *testing.T) { + // Test what happens if you use a valid image + avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) + assert.NoError(t, err) + if err != nil { + assert.FailNow(t, "Unable to open avatar.png") + } - req := NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - opts = api.UpdateUserAvatarOption{ - Image: "Invalid", - } - - req = NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) - - // Test what happens if you use a file that is not an image - opts = api.UpdateUserAvatarOption{ - Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")), - } - - req = NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusInternalServerError) -} - -func TestAPIDeleteUserAvatar(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - - req := NewRequest(t, "DELETE", "/api/v1/user/avatar"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + for _, avatarTest := range []struct { + image string + status int + }{ + {base64.StdEncoding.EncodeToString(avatar), http.StatusNoContent}, + // Test what happens if you don't have a valid Base64 string + {"Invalid", http.StatusBadRequest}, + // Test what happens if you use a file that is not an image + {base64.StdEncoding.EncodeToString([]byte("This is not an image")), http.StatusInternalServerError}, + } { + req := NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &api.UpdateUserAvatarOption{ + Image: avatarTest.image, + }).AddTokenAuth(token) + MakeRequest(t, req, avatarTest.status) + } + }) } diff --git a/tests/integration/api_user_block_test.go b/tests/integration/api_user_block_test.go index 56dc81cc0f1..ed7b0e05b0b 100644 --- a/tests/integration/api_user_block_test.go +++ b/tests/integration/api_user_block_test.go @@ -67,172 +67,105 @@ func TestBlockUser(t *testing.T) { return count } - t.Run("User", func(t *testing.T) { - var blockerID int64 = 16 - blockerName := "user16" - blockerToken := getUserToken(t, blockerName, auth_model.AccessTokenScopeWriteUser) + for _, blocker := range []struct { + name string + id int64 + username string + }{ + {"User", 16, "user16"}, + {"Organization", 3, "org3"}, + } { + t.Run(blocker.name, func(t *testing.T) { + var blockerToken string + blocksURL := "/api/v1/user/blocks" + counts := []func(*testing.T, int64, int64) int64{countStars, countWatches, countRepositoryTransfers} + if blocker.name == "User" { + blockerToken = getUserToken(t, blocker.username, auth_model.AccessTokenScopeWriteUser) + } else { + blockerToken = getUserToken(t, "user2", auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteOrganization) + blocksURL = fmt.Sprintf("/api/v1/orgs/%s/blocks", blocker.username) + counts = append(counts, countAssignedIssues) + } + counts = append(counts, countCollaborations) - var blockeeID int64 = 10 - blockeeName := "user10" + var blockeeID int64 = 10 + blockeeName := "user10" - t.Run("Block", func(t *testing.T) { - req := NewRequest(t, "PUT", "/api/v1/user/blocks/"+blockeeName) - MakeRequest(t, req, http.StatusUnauthorized) + t.Run("Block", func(t *testing.T) { + req := NewRequest(t, "PUT", blocksURL+"/"+blockeeName) + MakeRequest(t, req, http.StatusUnauthorized) - assert.EqualValues(t, 1, countStars(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countWatches(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countRepositoryTransfers(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countCollaborations(t, blockerID, blockeeID)) + if blocker.name == "Organization" { + req = NewRequest(t, "PUT", blocksURL+"/user4"). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) // can't block member + } - req = NewRequest(t, "GET", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusNotFound) + for _, count := range counts { + assert.EqualValues(t, 1, count(t, blocker.id, blockeeID)) + } - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s?reason=test", blockeeName)). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "GET", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusNotFound) - assert.EqualValues(t, 0, countStars(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countWatches(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countRepositoryTransfers(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countCollaborations(t, blockerID, blockeeID)) + req = NewRequest(t, "PUT", fmt.Sprintf("%s/%s?reason=test", blocksURL, blockeeName)). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusNoContent) + for _, count := range counts { + assert.EqualValues(t, 0, count(t, blocker.id, blockeeID)) + } - req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block blocked user + req = NewRequest(t, "GET", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+"org3"). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block organization + req = NewRequest(t, "PUT", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) // can't block blocked user - req = NewRequest(t, "GET", "/api/v1/user/blocks") - MakeRequest(t, req, http.StatusUnauthorized) + req = NewRequest(t, "PUT", blocksURL+"/org3"). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) // can't block organization - req = NewRequest(t, "GET", "/api/v1/user/blocks"). - AddTokenAuth(blockerToken) - resp := MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", blocksURL) + MakeRequest(t, req, http.StatusUnauthorized) - users := DecodeJSON(t, resp, []api.User{}) + req = NewRequest(t, "GET", blocksURL). + AddTokenAuth(blockerToken) + resp := MakeRequest(t, req, http.StatusOK) - assert.Len(t, users, 1) - assert.Equal(t, blockeeName, users[0].UserName) + users := DecodeJSON(t, resp, []api.User{}) + + assert.Len(t, users, 1) + assert.Equal(t, blockeeName, users[0].UserName) + }) + + t.Run("Unblock", func(t *testing.T) { + req := NewRequest(t, "DELETE", blocksURL+"/"+blockeeName) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "DELETE", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequest(t, "DELETE", blocksURL+"/org3"). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequest(t, "GET", blocksURL). + AddTokenAuth(blockerToken) + resp := MakeRequest(t, req, http.StatusOK) + + users := DecodeJSON(t, resp, []api.User{}) + + assert.Empty(t, users) + }) }) - - t.Run("Unblock", func(t *testing.T) { - req := NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+"org3"). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "GET", "/api/v1/user/blocks"). - AddTokenAuth(blockerToken) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - - assert.Empty(t, users) - }) - }) - - t.Run("Organization", func(t *testing.T) { - var blockerID int64 = 3 - blockerName := "org3" - - doerToken := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteOrganization) - - var blockeeID int64 = 10 - blockeeName := "user10" - - t.Run("Block", func(t *testing.T) { - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, "user4")). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block member - - assert.EqualValues(t, 1, countStars(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countWatches(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countRepositoryTransfers(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countAssignedIssues(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countCollaborations(t, blockerID, blockeeID)) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s?reason=test", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusNoContent) - - assert.EqualValues(t, 0, countStars(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countWatches(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countRepositoryTransfers(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countAssignedIssues(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countCollaborations(t, blockerID, blockeeID)) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block blocked user - - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, "org3")). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block organization - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks", blockerName)) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks", blockerName)). - AddTokenAuth(doerToken) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - - assert.Len(t, users, 1) - assert.Equal(t, blockeeName, users[0].UserName) - }) - - t.Run("Unblock", func(t *testing.T) { - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, "org3")). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks", blockerName)). - AddTokenAuth(doerToken) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - - assert.Empty(t, users) - }) - }) + } } diff --git a/tests/integration/api_user_email_test.go b/tests/integration/api_user_email_test.go index bc61a1d3939..99650db25e8 100644 --- a/tests/integration/api_user_email_test.go +++ b/tests/integration/api_user_email_test.go @@ -15,54 +15,6 @@ import ( "github.com/stretchr/testify/assert" ) -// TestAPIManageEmailsFeatureDisabled ensures the email management API honors the -// manage_credentials feature restriction, matching the web UI. -func TestAPIManageEmailsFeatureDisabled(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - - WithDisabledFeatures(t, setting.UserFeatureManageCredentials) - - addReq := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &api.CreateEmailOption{ - Emails: []string{"user2-3@example.com"}, - }).AddTokenAuth(token) - MakeRequest(t, addReq, http.StatusNotFound) - - delReq := NewRequestWithJSON(t, "DELETE", "/api/v1/user/emails", &api.DeleteEmailOption{ - Emails: []string{"user2-2@example.com"}, - }).AddTokenAuth(token) - MakeRequest(t, delReq, http.StatusNotFound) -} - -func TestAPIListEmails(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - - req := NewRequest(t, "GET", "/api/v1/user/emails"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - emails := DecodeJSON(t, resp, []*api.Email{}) - - assert.Equal(t, []*api.Email{ - { - Email: "user2@example.com", - Verified: true, - Primary: true, - }, - { - Email: "user2-2@example.com", - Verified: false, - Primary: false, - }, - }, emails) -} - func TestAPIAddEmail(t *testing.T) { defer tests.PrepareTestEnv(t)() @@ -70,46 +22,85 @@ func TestAPIAddEmail(t *testing.T) { session := loginUser(t, normalUsername) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - opts := api.CreateEmailOption{ - Emails: []string{"user101@example.com"}, - } + t.Run("ManageCredentialsDisabled", func(t *testing.T) { + WithDisabledFeatures(t, setting.UserFeatureManageCredentials) - req := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusConflict) + addReq := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &api.CreateEmailOption{ + Emails: []string{"user2-3@example.com"}, + }).AddTokenAuth(token) + MakeRequest(t, addReq, http.StatusNotFound) - opts = api.CreateEmailOption{ - Emails: []string{"user2-3@example.com"}, - } - req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) + delReq := NewRequestWithJSON(t, "DELETE", "/api/v1/user/emails", &api.DeleteEmailOption{ + Emails: []string{"user2-2@example.com"}, + }).AddTokenAuth(token) + MakeRequest(t, delReq, http.StatusNotFound) + }) - emails := DecodeJSON(t, resp, []*api.Email{}) - assert.Equal(t, []*api.Email{ - { - Email: "user2@example.com", - Verified: true, - Primary: true, - }, - { - Email: "user2-2@example.com", - Verified: false, - Primary: false, - }, - { - Email: "user2-3@example.com", - Verified: true, - Primary: false, - }, - }, emails) + t.Run("List", func(t *testing.T) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - opts = api.CreateEmailOption{ - Emails: []string{"notAEmail"}, - } - req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) + req := NewRequest(t, "GET", "/api/v1/user/emails"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + emails := DecodeJSON(t, resp, []*api.Email{}) + + assert.Equal(t, []*api.Email{ + { + Email: "user2@example.com", + Verified: true, + Primary: true, + }, + { + Email: "user2-2@example.com", + Verified: false, + Primary: false, + }, + }, emails) + }) + + t.Run("Add", func(t *testing.T) { + opts := api.CreateEmailOption{ + Emails: []string{"user101@example.com"}, + } + + req := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusConflict) + + opts = api.CreateEmailOption{ + Emails: []string{"user2-3@example.com"}, + } + req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + emails := DecodeJSON(t, resp, []*api.Email{}) + assert.Equal(t, []*api.Email{ + { + Email: "user2@example.com", + Verified: true, + Primary: true, + }, + { + Email: "user2-2@example.com", + Verified: false, + Primary: false, + }, + { + Email: "user2-3@example.com", + Verified: true, + Primary: false, + }, + }, emails) + + opts = api.CreateEmailOption{ + Emails: []string{"notAEmail"}, + } + req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusBadRequest) + }) } func TestAPIDeleteEmail(t *testing.T) { diff --git a/tests/integration/api_user_follow_test.go b/tests/integration/api_user_follow_test.go index 6a7b135b801..7aeec13a864 100644 --- a/tests/integration/api_user_follow_test.go +++ b/tests/integration/api_user_follow_test.go @@ -43,53 +43,29 @@ func TestAPIFollow(t *testing.T) { MakeRequest(t, req, http.StatusForbidden) }) - t.Run("ListFollowing", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, list := range []struct { + name string + url string + token string + username string + }{ + {"ListFollowing", fmt.Sprintf("/api/v1/users/%s/following", user2), token2, user1}, + {"ListMyFollowing", "/api/v1/user/following", token2, user1}, + {"ListFollowers", fmt.Sprintf("/api/v1/users/%s/followers", user1), token1, user2}, + {"ListMyFollowers", "/api/v1/user/followers", token1, user2}, + } { + t.Run(list.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/following", user2)). - AddTokenAuth(token2) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequest(t, "GET", list.url). + AddTokenAuth(list.token) + resp := MakeRequest(t, req, http.StatusOK) - users := DecodeJSON(t, resp, []api.User{}) - assert.Len(t, users, 1) - assert.Equal(t, user1, users[0].UserName) - }) - - t.Run("ListMyFollowing", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/following"). - AddTokenAuth(token2) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - assert.Len(t, users, 1) - assert.Equal(t, user1, users[0].UserName) - }) - - t.Run("ListFollowers", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/followers", user1)). - AddTokenAuth(token1) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - assert.Len(t, users, 1) - assert.Equal(t, user2, users[0].UserName) - }) - - t.Run("ListMyFollowers", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/followers"). - AddTokenAuth(token1) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - assert.Len(t, users, 1) - assert.Equal(t, user2, users[0].UserName) - }) + users := DecodeJSON(t, resp, []api.User{}) + assert.Len(t, users, 1) + assert.Equal(t, list.username, users[0].UserName) + }) + } t.Run("CheckFollowing", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_user_orgs_test.go b/tests/integration/api_user_orgs_test.go index 381ea43f52d..ca067098e24 100644 --- a/tests/integration/api_user_orgs_test.go +++ b/tests/integration/api_user_orgs_test.go @@ -19,69 +19,165 @@ import ( func TestUserOrgs(t *testing.T) { defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - normalUsername := "user2" - privateMemberUsername := "user4" - unrelatedUsername := "user5" - orgs := getUserOrgs(t, adminUsername, normalUsername) + t.Run("UserOrgs", func(t *testing.T) { + adminUsername := "user1" + normalUsername := "user2" + privateMemberUsername := "user4" + unrelatedUsername := "user5" - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) - org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) - org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) + orgs := getUserOrgs(t, adminUsername, normalUsername) - assert.Equal(t, []*api.Organization{ - { - ID: 17, - Name: org17.Name, - UserName: org17.Name, - FullName: org17.FullName, - Email: org17.Email, - AvatarURL: org17.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 3, - Name: org3.Name, - UserName: org3.Name, - FullName: org3.FullName, - Email: org3.Email, - AvatarURL: org3.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 35, - Name: org35.Name, - UserName: org35.Name, - FullName: org35.FullName, - Email: org35.Email, - AvatarURL: org35.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "private", - }, - }, orgs) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) + org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) + org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) - // user itself should get it's org's he is a member of - orgs = getUserOrgs(t, privateMemberUsername, privateMemberUsername) - assert.Len(t, orgs, 1) + assert.Equal(t, []*api.Organization{ + { + ID: 17, + Name: org17.Name, + UserName: org17.Name, + FullName: org17.FullName, + Email: org17.Email, + AvatarURL: org17.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 3, + Name: org3.Name, + UserName: org3.Name, + FullName: org3.FullName, + Email: org3.Email, + AvatarURL: org3.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 35, + Name: org35.Name, + UserName: org35.Name, + FullName: org35.FullName, + Email: org35.Email, + AvatarURL: org35.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "private", + }, + }, orgs) - // unrelated user should not get private org membership of privateMemberUsername - orgs = getUserOrgs(t, unrelatedUsername, privateMemberUsername) - assert.Empty(t, orgs) + // user itself should get it's org's he is a member of + orgs = getUserOrgs(t, privateMemberUsername, privateMemberUsername) + assert.Len(t, orgs, 1) - orgs = getUserOrgs(t, "user29", adminUsername) - assert.Empty(t, orgs) + // unrelated user should not get private org membership of privateMemberUsername + orgs = getUserOrgs(t, unrelatedUsername, privateMemberUsername) + assert.Empty(t, orgs) - // not authenticated call should not be allowed - testUserOrgsUnauthenticated(t, privateMemberUsername) + orgs = getUserOrgs(t, "user29", adminUsername) + assert.Empty(t, orgs) + + // not authenticated call should not be allowed + testUserOrgsUnauthenticated(t, privateMemberUsername) + }) + + t.Run("MyOrgs", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/user/orgs") + MakeRequest(t, req, http.StatusUnauthorized) + + normalUsername := "user2" + token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser) + req = NewRequest(t, "GET", "/api/v1/user/orgs"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + orgs := DecodeJSON(t, resp, []*api.Organization{}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) + org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) + org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) + + assert.Equal(t, []*api.Organization{ + { + ID: 17, + Name: org17.Name, + UserName: org17.Name, + FullName: org17.FullName, + Email: org17.Email, + AvatarURL: org17.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 3, + Name: org3.Name, + UserName: org3.Name, + FullName: org3.FullName, + Email: org3.Email, + AvatarURL: org3.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 35, + Name: org35.Name, + UserName: org35.Name, + FullName: org35.FullName, + Email: org35.Email, + AvatarURL: org35.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "private", + }, + }, orgs) + }) + + t.Run("MyOrgsPublicOnly", func(t *testing.T) { + normalUsername := "user2" + token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) + req := NewRequest(t, "GET", "/api/v1/user/orgs"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var orgs []*api.Organization + DecodeJSON(t, resp, &orgs) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) + org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) + + assert.Equal(t, []*api.Organization{ + { + ID: 17, + Name: org17.Name, + UserName: org17.Name, + FullName: org17.FullName, + Email: org17.Email, + AvatarURL: org17.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 3, + Name: org3.Name, + UserName: org3.Name, + FullName: org3.FullName, + Email: org3.Email, + AvatarURL: org3.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + }, orgs) + }) } func getUserOrgs(t *testing.T, userDoer, userCheck string) (orgs []*api.Organization) { @@ -101,100 +197,3 @@ func testUserOrgsUnauthenticated(t *testing.T, userCheck string) { req := NewRequestf(t, "GET", "/api/v1/users/%s/orgs", userCheck) session.MakeRequest(t, req, http.StatusUnauthorized) } - -func TestMyOrgs(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/api/v1/user/orgs") - MakeRequest(t, req, http.StatusUnauthorized) - - normalUsername := "user2" - token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser) - req = NewRequest(t, "GET", "/api/v1/user/orgs"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - orgs := DecodeJSON(t, resp, []*api.Organization{}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) - org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) - org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) - - assert.Equal(t, []*api.Organization{ - { - ID: 17, - Name: org17.Name, - UserName: org17.Name, - FullName: org17.FullName, - Email: org17.Email, - AvatarURL: org17.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 3, - Name: org3.Name, - UserName: org3.Name, - FullName: org3.FullName, - Email: org3.Email, - AvatarURL: org3.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 35, - Name: org35.Name, - UserName: org35.Name, - FullName: org35.FullName, - Email: org35.Email, - AvatarURL: org35.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "private", - }, - }, orgs) -} - -func TestMyOrgsPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/user/orgs"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var orgs []*api.Organization - DecodeJSON(t, resp, &orgs) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) - org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) - - assert.Equal(t, []*api.Organization{ - { - ID: 17, - Name: org17.Name, - UserName: org17.Name, - FullName: org17.FullName, - Email: org17.Email, - AvatarURL: org17.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 3, - Name: org3.Name, - UserName: org3.Name, - FullName: org3.FullName, - Email: org3.Email, - AvatarURL: org3.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - }, orgs) -} diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index 5cbf62ebec9..4a7be8b0434 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -21,133 +21,130 @@ type SearchResults struct { Data []*api.User `json:"data"` } -func TestAPIUserSearchLoggedIn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - session := loginUser(t, adminUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - query := "user2" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - for _, user := range results.Data { - assert.Contains(t, user.UserName, query) - assert.NotEmpty(t, user.Email) - } - - publicToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) - req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(publicToken) - resp = MakeRequest(t, req, http.StatusOK) - results = DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - for _, user := range results.Data { - assert.Contains(t, user.UserName, query) - assert.NotEmpty(t, user.Email) - assert.Equal(t, api.VisibilityStringPublic, user.Visibility) - } -} - -func TestAPIUserSearchNotLoggedIn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - query := "user2" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - var modelUser *user_model.User - for _, user := range results.Data { - assert.Contains(t, user.UserName, query) - modelUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID}) - assert.Equal(t, modelUser.GetPlaceholderEmail(), user.Email) - } -} - -func TestAPIUserSearchSystemUsers(t *testing.T) { - defer tests.PrepareTestEnv(t)() - for _, systemUser := range []*user_model.User{ - user_model.NewGhostUser(), - user_model.NewActionsUser(), - } { - t.Run(systemUser.Name, func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/users/search?uid=%d", systemUser.ID) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - if assert.Len(t, results.Data, 1) { - user := results.Data[0] - assert.Equal(t, user.UserName, systemUser.Name) - assert.Equal(t, user.ID, systemUser.ID) - } - }) - } -} - -func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - session := loginUser(t, adminUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - query := "user31" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - for _, user := range results.Data { - assert.Contains(t, user.UserName, query) - assert.NotEmpty(t, user.Email) - assert.Equal(t, api.VisibilityStringPrivate, user.Visibility) - } -} - -func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { - defer tests.PrepareTestEnv(t)() - query := "user31" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.Empty(t, results.Data) -} - -func TestAPIUserSearchByEmail(t *testing.T) { +func TestAPIUserSearch(t *testing.T) { defer tests.PrepareTestEnv(t)() - // admin can search user with private email - adminUsername := "user1" - session := loginUser(t, adminUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - query := "user2@example.com" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + t.Run("LoggedIn", func(t *testing.T) { + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + query := "user2" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - results := DecodeJSON(t, resp, &SearchResults{}) - assert.Len(t, results.Data, 1) - assert.Equal(t, query, results.Data[0].Email) + results := DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + } - // no login user can not search user with private email - req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) - resp = MakeRequest(t, req, http.StatusOK) - results = DecodeJSON(t, resp, &SearchResults{}) - assert.Empty(t, results.Data) + publicToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(publicToken) + resp = MakeRequest(t, req, http.StatusOK) + results = DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + assert.Equal(t, api.VisibilityStringPublic, user.Visibility) + } + }) - // user can search self with private email - user2 := "user2" - session = loginUser(t, user2) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) + t.Run("NotLoggedIn", func(t *testing.T) { + query := "user2" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp := MakeRequest(t, req, http.StatusOK) - results = DecodeJSON(t, resp, &SearchResults{}) - assert.Len(t, results.Data, 1) - assert.Equal(t, query, results.Data[0].Email) + results := DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + var modelUser *user_model.User + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + modelUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID}) + assert.Equal(t, modelUser.GetPlaceholderEmail(), user.Email) + } + }) + + t.Run("SystemUsers", func(t *testing.T) { + for _, systemUser := range []*user_model.User{ + user_model.NewGhostUser(), + user_model.NewActionsUser(), + } { + t.Run(systemUser.Name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/users/search?uid=%d", systemUser.ID) + resp := MakeRequest(t, req, http.StatusOK) + + results := DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + if assert.Len(t, results.Data, 1) { + user := results.Data[0] + assert.Equal(t, user.UserName, systemUser.Name) + assert.Equal(t, user.ID, systemUser.ID) + } + }) + } + }) + + t.Run("AdminLoggedInUserHidden", func(t *testing.T) { + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + query := "user31" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + results := DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + assert.Equal(t, api.VisibilityStringPrivate, user.Visibility) + } + }) + + t.Run("NotLoggedInUserHidden", func(t *testing.T) { + query := "user31" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp := MakeRequest(t, req, http.StatusOK) + + results := DecodeJSON(t, resp, &SearchResults{}) + assert.Empty(t, results.Data) + }) + + t.Run("ByEmail", func(t *testing.T) { + // admin can search user with private email + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + query := "user2@example.com" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + results := DecodeJSON(t, resp, &SearchResults{}) + assert.Len(t, results.Data, 1) + assert.Equal(t, query, results.Data[0].Email) + + // no login user can not search user with private email + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp = MakeRequest(t, req, http.StatusOK) + results = DecodeJSON(t, resp, &SearchResults{}) + assert.Empty(t, results.Data) + + // user can search self with private email + user2 := "user2" + session = loginUser(t, user2) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + + results = DecodeJSON(t, resp, &SearchResults{}) + assert.Len(t, results.Data, 1) + assert.Equal(t, query, results.Data[0].Email) + }) } diff --git a/tests/integration/api_user_star_test.go b/tests/integration/api_user_star_test.go index 7ea7f9f9715..626b010f56c 100644 --- a/tests/integration/api_user_star_test.go +++ b/tests/integration/api_user_star_test.go @@ -26,221 +26,161 @@ import ( func TestAPIStar(t *testing.T) { defer tests.PrepareTestEnv(t)() - user := "user1" - repo := "user2/repo1" - - session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - - t.Run("Star", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusNoContent) - - // blocked user can't star a repo - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). - AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("GetStarredRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starred", user)). + t.Run("PublicOnly", func(t *testing.T) { + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) + req := NewRequest(t, "GET", "/api/v1/user/starred"). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) - repos := DecodeJSON(t, resp, []api.Repository{}) - assert.Len(t, repos, 1) - assert.Equal(t, repo, repos[0].FullName) - }) + if assert.Len(t, repos, 1) { + assert.Equal(t, "user5/repo4", repos[0].FullName) + } - t.Run("GetMyStarredRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/starred"). - AddTokenAuth(tokenWithUserScope) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) - - repos := DecodeJSON(t, resp, []api.Repository{}) - assert.Len(t, repos, 1) - assert.Equal(t, repo, repos[0].FullName) - }) - - t.Run("IsStarring", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("Unstar", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusNoContent) - }) -} - -// TestAPIStarredReposAccessRevoked ensures a private repository disappears from a user's -// starred list once their access to it has been revoked, so no metadata leaks afterwards. -func TestAPIStarredReposAccessRevoked(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user2/repo2 is a private repository owned by user2 - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - - // grant the collaborator read access and star the private repo as them - require.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), repo, collaborator, perm.AccessModeRead)) - require.NoError(t, repo_model.StarRepo(t.Context(), collaborator, repo, true)) - - token := getUserToken(t, collaborator.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) - - // while access is granted, the private repo is visible in the starred list - req := NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - repos := DecodeJSON(t, resp, []api.Repository{}) - require.Len(t, repos, 1) - assert.Equal(t, repo.FullName(), repos[0].FullName) - - // revoke access - require.NoError(t, repo_service.DeleteCollaboration(t.Context(), repo, collaborator)) - - // the star record still exists, but the repo (and its metadata) must no longer be returned - assert.True(t, repo_model.IsStaring(t.Context(), collaborator.ID, repo.ID)) - req = NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - repos = DecodeJSON(t, resp, []api.Repository{}) - assert.Empty(t, repos) - - // sanity: the owner still sees their own private repo - ownerToken := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) - require.NoError(t, repo_model.StarRepo(t.Context(), owner, repo, true)) - req = NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(ownerToken) - resp = MakeRequest(t, req, http.StatusOK) - repos = DecodeJSON(t, resp, []api.Repository{}) - fullNames := make([]string, len(repos)) - for i, r := range repos { - fullNames[i] = r.FullName - } - assert.Contains(t, fullNames, repo.FullName()) -} - -func TestAPIStarDisabled(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := "user1" - repo := "user2/repo1" - - session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - - defer test.MockVariableValue(&setting.Repository.DisableStars, true)() - - t.Run("Star", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). - AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("GetStarredRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starred", user)). + req = NewRequest(t, "GET", "/api/v1/users/user2/starred"). AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("GetMyStarredRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/starred"). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("IsStarring", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - - req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("Unstar", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - }) -} - -func TestAPIStarPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/user/starred"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - repos := DecodeJSON(t, resp, []api.Repository{}) - if assert.Len(t, repos, 1) { + resp = MakeRequest(t, req, http.StatusOK) + repos = DecodeJSON(t, resp, []api.Repository{}) + require.Len(t, repos, 1) assert.Equal(t, "user5/repo4", repos[0].FullName) + }) + + for _, testCase := range []struct { + name string + disabled bool + writeStatus, listStatus, missingStatus int + }{ + {"Enabled", false, http.StatusNoContent, http.StatusOK, http.StatusNotFound}, + {"Disabled", true, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden}, + } { + t.Run(testCase.name, func(t *testing.T) { + user := "user1" + repo := "user2/repo1" + + session := loginUser(t, user) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + + if testCase.disabled { + defer test.MockVariableValue(&setting.Repository.DisableStars, true)() + } + + t.Run("Star", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, testCase.writeStatus) + + // blocked user can't star a repo + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). + AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) + MakeRequest(t, req, http.StatusForbidden) + }) + + for _, listCase := range []struct { + name, url, token string + }{ + {"GetStarredRepos", fmt.Sprintf("/api/v1/users/%s/starred", user), token}, + {"GetMyStarredRepos", "/api/v1/user/starred", tokenWithUserScope}, + } { + t.Run(listCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", listCase.url). + AddTokenAuth(listCase.token) + resp := MakeRequest(t, req, testCase.listStatus) + + if !testCase.disabled { + assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) + + repos := DecodeJSON(t, resp, []api.Repository{}) + assert.Len(t, repos, 1) + assert.Equal(t, repo, repos[0].FullName) + } + }) + } + + t.Run("IsStarring", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, testCase.writeStatus) + + req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, testCase.missingStatus) + }) + + t.Run("Unstar", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, testCase.writeStatus) + }) + }) } - req = NewRequest(t, "GET", "/api/v1/users/user2/starred"). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - repos = DecodeJSON(t, resp, []api.Repository{}) - require.Len(t, repos, 1) - assert.Equal(t, "user5/repo4", repos[0].FullName) -} - -func TestAPIStarredReposOmitsInaccessiblePrivateRepos(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - privateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, privateRepo.IsPrivate) - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), privateRepo, user, perm.AccessModeRead)) - assert.NoError(t, repo_model.StarRepo(t.Context(), user, privateRepo, true)) - assert.NoError(t, repo_service.DeleteCollaboration(t.Context(), privateRepo, user)) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) - req := NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - repos := DecodeJSON(t, resp, []api.Repository{}) - for _, repo := range repos { - assert.NotEqual(t, privateRepo.FullName(), repo.FullName) - } + t.Run("AccessRevoked", func(t *testing.T) { + // user2/repo2 is a private repository owned by user2 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + + // grant the collaborator read access and star the private repo as them + require.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), repo, collaborator, perm.AccessModeRead)) + require.NoError(t, repo_model.StarRepo(t.Context(), collaborator, repo, true)) + + token := getUserToken(t, collaborator.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + + // while access is granted, the private repo is visible in the starred list + req := NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + repos := DecodeJSON(t, resp, []api.Repository{}) + require.Len(t, repos, 1) + assert.Equal(t, repo.FullName(), repos[0].FullName) + + // revoke access + require.NoError(t, repo_service.DeleteCollaboration(t.Context(), repo, collaborator)) + + // the star record still exists, but the repo (and its metadata) must no longer be returned + assert.True(t, repo_model.IsStaring(t.Context(), collaborator.ID, repo.ID)) + req = NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + repos = DecodeJSON(t, resp, []api.Repository{}) + assert.Empty(t, repos) + + // sanity: the owner still sees their own private repo + ownerToken := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + require.NoError(t, repo_model.StarRepo(t.Context(), owner, repo, true)) + req = NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(ownerToken) + resp = MakeRequest(t, req, http.StatusOK) + repos = DecodeJSON(t, resp, []api.Repository{}) + fullNames := make([]string, len(repos)) + for i, r := range repos { + fullNames[i] = r.FullName + } + assert.Contains(t, fullNames, repo.FullName()) + }) + + t.Run("OmitsInaccessiblePrivateRepos", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + privateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, privateRepo.IsPrivate) + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), privateRepo, user, perm.AccessModeRead)) + assert.NoError(t, repo_model.StarRepo(t.Context(), user, privateRepo, true)) + assert.NoError(t, repo_service.DeleteCollaboration(t.Context(), privateRepo, user)) + + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + req := NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + repos := DecodeJSON(t, resp, []api.Repository{}) + for _, repo := range repos { + assert.NotEqual(t, privateRepo.FullName(), repo.FullName) + } + }) } diff --git a/tests/integration/api_user_update_test.go b/tests/integration/api_user_update_test.go deleted file mode 100644 index a3cdc2558af..00000000000 --- a/tests/integration/api_user_update_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/tests" -) - -func TestAPIUpdateUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - - req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", map[string]string{ - "website": "https://gitea.com", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) -} diff --git a/tests/integration/api_user_variables_test.go b/tests/integration/api_user_variables_test.go index 00805ae3fe4..3b6137dd086 100644 --- a/tests/integration/api_user_variables_test.go +++ b/tests/integration/api_user_variables_test.go @@ -15,6 +15,17 @@ import ( func TestAPIUserVariables(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("UpdateUser", func(t *testing.T) { + normalUsername := "user2" + session := loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) + + req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", map[string]string{ + "website": "https://gitea.com", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + }) + session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) diff --git a/tests/integration/api_user_watch_test.go b/tests/integration/api_user_watch_test.go index 073eb0ed0b7..3e907832af7 100644 --- a/tests/integration/api_user_watch_test.go +++ b/tests/integration/api_user_watch_test.go @@ -41,33 +41,25 @@ func TestAPIWatch(t *testing.T) { MakeRequest(t, req, http.StatusForbidden) }) - t.Run("GetWatchedRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, tc := range []struct { + name, url, token string + }{ + {"GetWatchedRepos", fmt.Sprintf("/api/v1/users/%s/subscriptions", user), token}, + {"GetMyWatchedRepos", "/api/v1/user/subscriptions", tokenWithRepoScope}, + } { + t.Run(tc.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/subscriptions", user)). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequest(t, "GET", tc.url).AddTokenAuth(tc.token) + resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) - repos := DecodeJSON(t, resp, []api.Repository{}) - assert.Len(t, repos, 1) - assert.Equal(t, repo, repos[0].FullName) - }) - - t.Run("GetMyWatchedRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/subscriptions"). - AddTokenAuth(tokenWithRepoScope) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) - - repos := DecodeJSON(t, resp, []api.Repository{}) - assert.Len(t, repos, 1) - assert.Equal(t, repo, repos[0].FullName) - }) + repos := DecodeJSON(t, resp, []api.Repository{}) + assert.Len(t, repos, 1) + assert.Equal(t, repo, repos[0].FullName) + }) + } t.Run("IsWatching", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -84,6 +76,27 @@ func TestAPIWatch(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) }) + t.Run("PublicOnly", func(t *testing.T) { + publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + + MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo1/subscription").AddTokenAuth(tokenWithRepoScope), http.StatusOK) + MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo2/subscription").AddTokenAuth(tokenWithRepoScope), http.StatusOK) + + resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK) + repos := DecodeJSON(t, resp, []api.Repository{}) + for _, r := range repos { + assert.False(t, r.Private, "private repo %s leaked via /user/subscriptions", r.FullName) + } + assert.NotContains(t, repoNames(repos), "user2/repo2") + + resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user1/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK) + repos = DecodeJSON(t, resp, []api.Repository{}) + for _, r := range repos { + assert.False(t, r.Private, "private repo %s leaked via /users/{username}/subscriptions", r.FullName) + } + assert.NotContains(t, repoNames(repos), "user2/repo2") + }) + t.Run("Unwatch", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -92,28 +105,3 @@ func TestAPIWatch(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) }) } - -func TestAPIWatchPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user1") - writeRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadUser) - publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) - - MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo1/subscription").AddTokenAuth(writeRepoToken), http.StatusOK) - MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo2/subscription").AddTokenAuth(writeRepoToken), http.StatusOK) - - resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK) - repos := DecodeJSON(t, resp, []api.Repository{}) - for _, r := range repos { - assert.False(t, r.Private, "private repo %s leaked via /user/subscriptions", r.FullName) - } - assert.NotContains(t, repoNames(repos), "user2/repo2") - - resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user1/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK) - repos = DecodeJSON(t, resp, []api.Repository{}) - for _, r := range repos { - assert.False(t, r.Private, "private repo %s leaked via /users/{username}/subscriptions", r.FullName) - } - assert.NotContains(t, repoNames(repos), "user2/repo2") -} diff --git a/tests/integration/api_wiki_test.go b/tests/integration/api_wiki_test.go index 2ae617c6cff..2b09906a0f1 100644 --- a/tests/integration/api_wiki_test.go +++ b/tests/integration/api_wiki_test.go @@ -19,155 +19,168 @@ import ( func TestAPIGetWikiPage(t *testing.T) { defer tests.PrepareTestEnv(t)() - username := "user2" + t.Run("GetWikiPage", func(t *testing.T) { + username := "user2" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Home", username, "repo1") + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Home", username, "repo1") - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - page := DecodeJSON(t, resp, &api.WikiPage{}) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + page := DecodeJSON(t, resp, &api.WikiPage{}) - assert.Equal(t, &api.WikiPage{ - WikiPageMetaData: &api.WikiPageMetaData{ - Title: "Home", - HTMLURL: page.HTMLURL, - SubURL: "Home", - LastCommit: &api.WikiCommit{ - ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", + expectedPage := &api.WikiPage{ + WikiPageMetaData: &api.WikiPageMetaData{ + Title: "Home", + HTMLURL: page.HTMLURL, + SubURL: "Home", + LastCommit: &api.WikiCommit{ + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", }, - Date: "2017-11-27T04:31:18Z", + Message: "Add Home.md\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", - }, - Date: "2017-11-27T04:31:18Z", - }, - Message: "Add Home.md\n", }, - }, - ContentBase64: base64.RawStdEncoding.EncodeToString( - []byte("# Home page\n\nThis is the home page!\n"), - ), - CommitCount: 1, - Sidebar: "", - Footer: "", - }, page) -} + ContentBase64: base64.RawStdEncoding.EncodeToString( + []byte("# Home page\n\nThis is the home page!\n"), + ), + CommitCount: 1, + Sidebar: "", + Footer: "", + } + expectedPage.LastCommit.Committer = expectedPage.LastCommit.Author + assert.Equal(t, expectedPage, page) + }) + t.Run("ListWikiPages", func(t *testing.T) { + username := "user2" -func TestAPIListWikiPages(t *testing.T) { - defer tests.PrepareTestEnv(t)() + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", username, "repo1") - username := "user2" + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", username, "repo1") + meta := DecodeJSON(t, resp, []*api.WikiPageMetaData{}) - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - meta := DecodeJSON(t, resp, []*api.WikiPageMetaData{}) - - dummymeta := []*api.WikiPageMetaData{ - { - Title: "Home", - HTMLURL: meta[0].HTMLURL, - SubURL: "Home", - LastCommit: &api.WikiCommit{ - ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", + dummymeta := []*api.WikiPageMetaData{ + { + Title: "Home", + HTMLURL: meta[0].HTMLURL, + SubURL: "Home", + LastCommit: &api.WikiCommit{ + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", }, - Date: "2017-11-27T04:31:18Z", + Message: "Add Home.md\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", - }, - Date: "2017-11-27T04:31:18Z", - }, - Message: "Add Home.md\n", }, - }, - { - Title: "Page With Image", - HTMLURL: meta[1].HTMLURL, - SubURL: "Page-With-Image", - LastCommit: &api.WikiCommit{ - ID: "0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Gabriel Silva Simões", - Email: "simoes.sgabriel@gmail.com", + { + Title: "Page With Image", + HTMLURL: meta[1].HTMLURL, + SubURL: "Page-With-Image", + LastCommit: &api.WikiCommit{ + ID: "0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Gabriel Silva Simões", + Email: "simoes.sgabriel@gmail.com", + }, + Date: "2019-01-25T01:41:55Z", }, - Date: "2019-01-25T01:41:55Z", + Message: "Add jpeg.jpg and page with image\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Gabriel Silva Simões", - Email: "simoes.sgabriel@gmail.com", - }, - Date: "2019-01-25T01:41:55Z", - }, - Message: "Add jpeg.jpg and page with image\n", }, - }, - { - Title: "Page With Spaced Name", - HTMLURL: meta[2].HTMLURL, - SubURL: "Page-With-Spaced-Name", - LastCommit: &api.WikiCommit{ - ID: "c10d10b7e655b3dab1f53176db57c8219a5488d6", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Gabriel Silva Simões", - Email: "simoes.sgabriel@gmail.com", + { + Title: "Page With Spaced Name", + HTMLURL: meta[2].HTMLURL, + SubURL: "Page-With-Spaced-Name", + LastCommit: &api.WikiCommit{ + ID: "c10d10b7e655b3dab1f53176db57c8219a5488d6", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Gabriel Silva Simões", + Email: "simoes.sgabriel@gmail.com", + }, + Date: "2019-01-25T01:39:51Z", }, - Date: "2019-01-25T01:39:51Z", + Message: "Add page with spaced name\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Gabriel Silva Simões", - Email: "simoes.sgabriel@gmail.com", - }, - Date: "2019-01-25T01:39:51Z", - }, - Message: "Add page with spaced name\n", }, - }, - { - Title: "Unescaped File", - HTMLURL: meta[3].HTMLURL, - SubURL: "Unescaped-File", - LastCommit: &api.WikiCommit{ - ID: "0dca5bd9b5d7ef937710e056f575e86c0184ba85", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "6543", - Email: "6543@obermui.de", + { + Title: "Unescaped File", + HTMLURL: meta[3].HTMLURL, + SubURL: "Unescaped-File", + LastCommit: &api.WikiCommit{ + ID: "0dca5bd9b5d7ef937710e056f575e86c0184ba85", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "6543", + Email: "6543@obermui.de", + }, + Date: "2021-07-19T16:42:46Z", }, - Date: "2021-07-19T16:42:46Z", + Message: "add unescaped file\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "6543", - Email: "6543@obermui.de", - }, - Date: "2021-07-19T16:42:46Z", - }, - Message: "add unescaped file\n", }, - }, - } + } - assert.Equal(t, dummymeta, meta) + for _, page := range dummymeta { + page.LastCommit.Committer = page.LastCommit.Author + } + assert.Equal(t, dummymeta, meta) + }) + t.Run("ListPageRevisions", func(t *testing.T) { + username := "user2" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/revisions/Home", username, "repo1") + + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + revisions := DecodeJSON(t, resp, &api.WikiCommitList{}) + + dummyrevisions := &api.WikiCommitList{ + WikiCommits: []*api.WikiCommit{ + { + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", + }, + Message: "Add Home.md\n", + }, + }, + Count: 1, + } + + dummyrevisions.WikiCommits[0].Committer = dummyrevisions.WikiCommits[0].Author + assert.Equal(t, dummyrevisions, revisions) + }) + t.Run("EditWikiPage", func(t *testing.T) { + username := "user2" + session := loginUser(t, username) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Page-With-Spaced-Name", username, "repo1") + + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.CreateWikiPageOptions{ + Title: "edited title", + ContentBase64: base64.StdEncoding.EncodeToString([]byte("Edited wiki page content for API unit tests")), + Message: "", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + }) } func testAPICreateWikiPage(t *testing.T, session *TestSession, userName, repoName, title string, status int) { @@ -194,57 +207,3 @@ func TestAPINewWikiPage(t *testing.T) { testAPICreateWikiPage(t, session, username, "repo1", title, http.StatusCreated) } } - -func TestAPIEditWikiPage(t *testing.T) { - defer tests.PrepareTestEnv(t)() - username := "user2" - session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Page-With-Spaced-Name", username, "repo1") - - req := NewRequestWithJSON(t, "PATCH", urlStr, &api.CreateWikiPageOptions{ - Title: "edited title", - ContentBase64: base64.StdEncoding.EncodeToString([]byte("Edited wiki page content for API unit tests")), - Message: "", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) -} - -func TestAPIListPageRevisions(t *testing.T) { - defer tests.PrepareTestEnv(t)() - username := "user2" - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/revisions/Home", username, "repo1") - - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - revisions := DecodeJSON(t, resp, &api.WikiCommitList{}) - - dummyrevisions := &api.WikiCommitList{ - WikiCommits: []*api.WikiCommit{ - { - ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", - }, - Date: "2017-11-27T04:31:18Z", - }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", - }, - Date: "2017-11-27T04:31:18Z", - }, - Message: "Add Home.md\n", - }, - }, - Count: 1, - } - - assert.Equal(t, dummyrevisions, revisions) -} diff --git a/tests/integration/attachment_test.go b/tests/integration/attachment_test.go index 58ac34095c4..ac8d653e198 100644 --- a/tests/integration/attachment_test.go +++ b/tests/integration/attachment_test.go @@ -27,15 +27,6 @@ import ( "github.com/stretchr/testify/require" ) -type attachmentScopeCase struct { - name string - url string - readIssueStatus int - readRepoStatus int - publicOnlyIssueStatus int - publicOnlyRepoStatus int -} - func testGeneratePngBytes() []byte { myImage := image.NewRGBA(image.Rect(0, 0, 32, 32)) var buff bytes.Buffer @@ -86,6 +77,88 @@ func testDeleteReleaseAttachment(t *testing.T, session *TestSession, repoURL, uu func TestAttachments(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("TokenScopes", func(t *testing.T) { + for _, uuid := range []string{ + "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", + "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", + "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", + } { + _, err := storage.Attachments.Save(repo_model.AttachmentRelativePath(uuid), strings.NewReader("hello world"), -1) + require.NoError(t, err) + } + + session := loginUser(t, "user2") + readIssueToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + readRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + miscToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadMisc) + publicOnlyIssueToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly) + publicOnlyRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) + + cases := []struct { + name string + url string + repoPath string + readIssueStatus int + readRepoStatus int + publicOnlyIssueStatus int + publicOnlyRepoStatus int + }{ + { + name: "PublicIssueAttachment", + url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + repoPath: "/user2/repo1", + readIssueStatus: http.StatusOK, + readRepoStatus: http.StatusForbidden, + publicOnlyIssueStatus: http.StatusOK, + publicOnlyRepoStatus: http.StatusForbidden, + }, + { + name: "PrivateIssueAttachment", + url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", + repoPath: "/user2/repo2", + readIssueStatus: http.StatusOK, + readRepoStatus: http.StatusForbidden, + publicOnlyIssueStatus: http.StatusNotFound, + publicOnlyRepoStatus: http.StatusNotFound, + }, + { + name: "PublicReleaseAttachment", + url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", + repoPath: "/user2/repo1/releases", + readIssueStatus: http.StatusForbidden, + readRepoStatus: http.StatusOK, + publicOnlyIssueStatus: http.StatusForbidden, + publicOnlyRepoStatus: http.StatusOK, + }, + { + name: "PrivateReleaseAttachment", + url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", + repoPath: "/user2/repo2/releases", + readIssueStatus: http.StatusForbidden, + readRepoStatus: http.StatusOK, + publicOnlyIssueStatus: http.StatusNotFound, + publicOnlyRepoStatus: http.StatusNotFound, + }, + } + + for _, tc := range cases { + for _, route := range []struct { + name, url string + }{ + {"Global", tc.url}, + {"Repo", tc.repoPath + tc.url}, + } { + t.Run(route.name+tc.name, func(t *testing.T) { + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(miscToken), http.StatusForbidden) + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(readIssueToken), tc.readIssueStatus) + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(readRepoToken), tc.readRepoStatus) + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(publicOnlyIssueToken), tc.publicOnlyIssueStatus) + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(publicOnlyRepoToken), tc.publicOnlyRepoStatus) + }) + } + } + }) t.Run("CreateAnonymousAttachment", testCreateAnonymousAttachment) t.Run("CreateUser2IssueAttachment", testCreateUser2IssueAttachment) t.Run("UploadAttachmentDeleteTemp", testUploadAttachmentDeleteTemp) @@ -235,100 +308,3 @@ func testDeleteAttachmentPermissions(t *testing.T) { // test deleting release attachment from another repo testDeleteReleaseAttachment(t, ownerSession, "/user2/repo2", crossRepoUUID, http.StatusBadRequest) } - -func TestAttachmentTokenScopes(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - for _, uuid := range []string{ - "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", - "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", - "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", - "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", - } { - _, err := storage.Attachments.Save(repo_model.AttachmentRelativePath(uuid), strings.NewReader("hello world"), -1) - require.NoError(t, err) - } - - readIssueToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue) - readRepoToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) - miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) - publicOnlyIssueToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly) - publicOnlyRepoToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - - cases := []attachmentScopeCase{ - { - name: "GlobalPublicIssueAttachment", - url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", - readIssueStatus: http.StatusOK, - readRepoStatus: http.StatusForbidden, - publicOnlyIssueStatus: http.StatusOK, - publicOnlyRepoStatus: http.StatusForbidden, - }, - { - name: "RepoPublicIssueAttachment", - url: "/user2/repo1/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", - readIssueStatus: http.StatusOK, - readRepoStatus: http.StatusForbidden, - publicOnlyIssueStatus: http.StatusOK, - publicOnlyRepoStatus: http.StatusForbidden, - }, - { - name: "GlobalPrivateIssueAttachment", - url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", - readIssueStatus: http.StatusOK, - readRepoStatus: http.StatusForbidden, - publicOnlyIssueStatus: http.StatusNotFound, - publicOnlyRepoStatus: http.StatusNotFound, - }, - { - name: "RepoPrivateIssueAttachment", - url: "/user2/repo2/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", - readIssueStatus: http.StatusOK, - readRepoStatus: http.StatusForbidden, - publicOnlyIssueStatus: http.StatusNotFound, - publicOnlyRepoStatus: http.StatusNotFound, - }, - { - name: "GlobalPublicReleaseAttachment", - url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", - readIssueStatus: http.StatusForbidden, - readRepoStatus: http.StatusOK, - publicOnlyIssueStatus: http.StatusForbidden, - publicOnlyRepoStatus: http.StatusOK, - }, - { - name: "RepoPublicReleaseAttachment", - url: "/user2/repo1/releases/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", - readIssueStatus: http.StatusForbidden, - readRepoStatus: http.StatusOK, - publicOnlyIssueStatus: http.StatusForbidden, - publicOnlyRepoStatus: http.StatusOK, - }, - { - name: "GlobalPrivateReleaseAttachment", - url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", - readIssueStatus: http.StatusForbidden, - readRepoStatus: http.StatusOK, - publicOnlyIssueStatus: http.StatusNotFound, - publicOnlyRepoStatus: http.StatusNotFound, - }, - { - name: "RepoPrivateReleaseAttachment", - url: "/user2/repo2/releases/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", - readIssueStatus: http.StatusForbidden, - readRepoStatus: http.StatusOK, - publicOnlyIssueStatus: http.StatusNotFound, - publicOnlyRepoStatus: http.StatusNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(miscToken), http.StatusForbidden) - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(readIssueToken), tc.readIssueStatus) - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(readRepoToken), tc.readRepoStatus) - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(publicOnlyIssueToken), tc.publicOnlyIssueStatus) - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(publicOnlyRepoToken), tc.publicOnlyRepoStatus) - }) - } -} diff --git a/tests/integration/auth_oauth2_test.go b/tests/integration/auth_oauth2_test.go index c65854c15bf..9ae37d73731 100644 --- a/tests/integration/auth_oauth2_test.go +++ b/tests/integration/auth_oauth2_test.go @@ -29,122 +29,361 @@ import ( "xorm.io/builder" ) -// TestMigrateAzureADV2ToOIDC simulates a login source migration from the Azure AD V2 OAuth2 provider to the OpenID Connect provider, -// and verifies that setting ExternalIDClaim = "oid" restores account continuity. -// -// Background: Azure AD V2 (goth's azureadv2 provider) fetches the user profile from Microsoft Graph API (/v1.0/me) -// and uses the "id" field - the stable Object ID (OID) - as gothUser.UserID. That OID is stored as ExternalID in external_login_user. -// -// When the admin migrates the same source to OpenID Connect, the goth openidConnect provider defaults to ["sub"] for UserIdClaims. -// Azure AD's "sub" is pairwise (unique per application), so it differs from the OID that was previously stored, -// causing every existing user to appear as a new account. -// -// Setting ExternalIDClaim = "oid" on the OIDC source overrides UserIdClaims to ["oid"], -// so the same OID is extracted and matched against the existing rows, restoring continuity. -func TestMigrateAzureADV2ToOIDC(t *testing.T) { +func TestAuthOAuth2Flows(t *testing.T) { defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - // Use UserID (gothUser.UserID) as the Gitea username so that different ExternalID values produce different, non-conflicting usernames. - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() - const ( - sourceName = "test-migrate-azure" + // TestMigrateAzureADV2ToOIDC simulates a login source migration from the Azure AD V2 OAuth2 provider to the OpenID Connect provider, + // and verifies that setting ExternalIDClaim = "oid" restores account continuity. + // + // Background: Azure AD V2 (goth's azureadv2 provider) fetches the user profile from Microsoft Graph API (/v1.0/me) + // and uses the "id" field - the stable Object ID (OID) - as gothUser.UserID. That OID is stored as ExternalID in external_login_user. + // + // When the admin migrates the same source to OpenID Connect, the goth openidConnect provider defaults to ["sub"] for UserIdClaims. + // Azure AD's "sub" is pairwise (unique per application), so it differs from the OID that was previously stored, + // causing every existing user to appear as a new account. + // + // Setting ExternalIDClaim = "oid" on the OIDC source overrides UserIdClaims to ["oid"], + // so the same OID is extracted and matched against the existing rows, restoring continuity. + t.Run("MigrateAzureADV2ToOIDC", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + // Use UserID (gothUser.UserID) as the Gitea username so that different ExternalID values produce different, non-conflicting usernames. + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() - // oidValue is the stable Azure AD Object ID, used as ExternalID by the Azure AD V2 provider. - oidValue = "oid-object-id-stable" + const ( + sourceName = "test-migrate-azure" - // subValue is the pairwise sub issued by Azure AD for OpenID Connect; it differs from oidValue and would produce a separate account if used. - subValue = "sub-pairwise-value" - ) + // oidValue is the stable Azure AD Object ID, used as ExternalID by the Azure AD V2 provider. + oidValue = "oid-object-id-stable" - // The fake OIDC server issues tokens containing both sub and oid claims, mirroring what Azure AD v2.0 returns. - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: subValue, OID: oidValue}) + // subValue is the pairwise sub issued by Azure AD for OpenID Connect; it differs from oidValue and would produce a separate account if used. + subValue = "sub-pairwise-value" + ) - // --- Step 1: Establish the legacy Azure AD V2 state --- - // Create an azureadv2 auth source. In production this would have been the source used before the migration. - addOAuth2Source(t, sourceName, oauth2.Source{ - Provider: "azureadv2", - ClientID: "test-client-id", - ClientSecret: "test-client-secret", - CustomURLMapping: &oauth2.CustomURLMapping{ - Tenant: "test-tenant-id", - }, - }) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) - require.NoError(t, err) + // The fake OIDC server issues tokens containing both sub and oid claims, mirroring what Azure AD v2.0 returns. + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: subValue, OID: oidValue}) - // Create a user to represent the "legacy" account that was originally registered through the Azure AD V2 provider. - legacyUser := &user_model.User{ - Name: "legacy-azure-user", - Email: "legacy-azure-user@example.com", - } - require.NoError(t, user_model.CreateUser(t.Context(), legacyUser, &user_model.Meta{})) - require.NoError(t, user_model.LinkExternalToUser(t.Context(), legacyUser, &user_model.ExternalLoginUser{ - ExternalID: oidValue, - UserID: legacyUser.ID, - LoginSourceID: authSource.ID, - Provider: authSource.Name, - })) + // --- Step 1: Establish the legacy Azure AD V2 state --- + // Create an azureadv2 auth source. In production this would have been the source used before the migration. + addOAuth2Source(t, sourceName, oauth2.Source{ + Provider: "azureadv2", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + Tenant: "test-tenant-id", + }, + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) + require.NoError(t, err) - // --- Step 2: Migrate the source to OIDC without ExternalIDClaim --- - // The provider type of the OAuth2 source is changed from azureadv2 to openidConnect. - // Without ExternalIDClaim the goth provider defaults to "sub", which does not match the stored OID, so every sign-in creates a fresh account. - authSource.Cfg = &oauth2.Source{ - Provider: "openidConnect", - ClientID: "test-client-id", - ClientSecret: "test-client-secret", - OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", - // ExternalIDClaim intentionally not set; goth defaults to "sub". - } - err = auth_model.UpdateSource(t.Context(), authSource) - require.NoError(t, err) + // Create a user to represent the "legacy" account that was originally registered through the Azure AD V2 provider. + legacyUser := &user_model.User{ + Name: "legacy-azure-user", + Email: "legacy-azure-user@example.com", + } + require.NoError(t, user_model.CreateUser(t.Context(), legacyUser, &user_model.Meta{})) + require.NoError(t, user_model.LinkExternalToUser(t.Context(), legacyUser, &user_model.ExternalLoginUser{ + ExternalID: oidValue, + UserID: legacyUser.ID, + LoginSourceID: authSource.ID, + Provider: authSource.Name, + })) - t.Run("without ExternalIDClaim: legacy user is NOT matched", func(t *testing.T) { - // Confirm the external user with ExternalID=subValue doesn't exist. - unittest.AssertNotExistsBean(t, &user_model.ExternalLoginUser{ExternalID: subValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + // --- Step 2: Migrate the source to OIDC without ExternalIDClaim --- + // The provider type of the OAuth2 source is changed from azureadv2 to openidConnect. + // Without ExternalIDClaim the goth provider defaults to "sub", which does not match the stored OID, so every sign-in creates a fresh account. + authSource.Cfg = &oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", + // ExternalIDClaim intentionally not set; goth defaults to "sub". + } + err = auth_model.UpdateSource(t.Context(), authSource) + require.NoError(t, err) - doOIDCSignIn(t, sourceName) + t.Run("without ExternalIDClaim: legacy user is NOT matched", func(t *testing.T) { + // Confirm the external user with ExternalID=subValue doesn't exist. + unittest.AssertNotExistsBean(t, &user_model.ExternalLoginUser{ExternalID: subValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - // "sub" is now the ExternalID - a new user was auto-registered. - subEntry := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: subValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - // The auto-registered user is NOT the legacy user. - assert.NotEqual(t, legacyUser.ID, subEntry.UserID) + doOIDCSignIn(t, sourceName) + + // "sub" is now the ExternalID - a new user was auto-registered. + subEntry := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: subValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + // The auto-registered user is NOT the legacy user. + assert.NotEqual(t, legacyUser.ID, subEntry.UserID) + }) + + // --- Step 3: Set ExternalIDClaim = "oid" to restore account continuity --- + // Set ExternalIDClaim = "oid" so that the OIDC source extracts the same Object ID that the Azure AD V2 provider previously stored. + oauth2Source, ok := authSource.Cfg.(*oauth2.Source) + require.True(t, ok) + oauth2Source.ExternalIDClaim = "oid" + err = auth_model.UpdateSource(t.Context(), authSource) + require.NoError(t, err) + + t.Run("with ExternalIDClaim=oid: legacy user IS matched", func(t *testing.T) { + // Confirm the legacy oid row has no RawData yet - it was created directly via LinkExternalToUser in setup, without going through an OAuth flow. + oidEntry := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: oidValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + require.Nil(t, oidEntry.RawData) + + doOIDCSignIn(t, sourceName) + + // After sign-in, RawData should contain both "oid" and "name". + oidEntry = unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: oidValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + assert.Equal(t, oidValue, oidEntry.RawData["oid"]) + assert.Equal(t, "OIDC Test User", oidEntry.RawData["name"]) + + // The matched user must still be the original legacy user. + assert.Equal(t, legacyUser.ID, oidEntry.UserID) + }) }) - // --- Step 3: Set ExternalIDClaim = "oid" to restore account continuity --- - // Set ExternalIDClaim = "oid" so that the OIDC source extracts the same Object ID that the Azure AD V2 provider previously stored. - oauth2Source, ok := authSource.Cfg.(*oauth2.Source) - require.True(t, ok) - oauth2Source.ExternalIDClaim = "oid" - err = auth_model.UpdateSource(t.Context(), authSource) - require.NoError(t, err) + t.Run("OIDCIgnoresStaleExternalLoginLinks", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() - t.Run("with ExternalIDClaim=oid: legacy user IS matched", func(t *testing.T) { - // Confirm the legacy oid row has no RawData yet - it was created directly via LinkExternalToUser in setup, without going through an OAuth flow. - oidEntry := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: oidValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - require.Nil(t, oidEntry.RawData) + for _, tc := range []struct { + name, sourceName, sub, userName string + staleUserID int64 + }{ + {"organization", "test-oidc-stale-org-link", "oidc-stale-org-link-sub", "guizar_m", 3}, + // link the external account to a user id that does not exist, simulating a deleted user + {"deleted user", "test-oidc-stale-deleted", "oidc-stale-deleted-link-sub", "guizar_d", 999999}, + } { + t.Run(tc.name, func(t *testing.T) { + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: tc.sub, OID: tc.sub + "-oid", Email: tc.userName + "@example.com", Name: "OIDC Test User"}) + addOAuth2Source(t, tc.sourceName, oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), tc.sourceName) + require.NoError(t, err) + correctUser := &user_model.User{Name: tc.userName, Email: tc.userName + "@example.com"} + require.NoError(t, user_model.CreateUser(t.Context(), correctUser, &user_model.Meta{})) - doOIDCSignIn(t, sourceName) - - // After sign-in, RawData should contain both "oid" and "name". - oidEntry = unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: oidValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - assert.Equal(t, oidValue, oidEntry.RawData["oid"]) - assert.Equal(t, "OIDC Test User", oidEntry.RawData["name"]) - - // The matched user must still be the original legacy user. - assert.Equal(t, legacyUser.ID, oidEntry.UserID) + staleUser := &user_model.User{ID: tc.staleUserID} + if tc.staleUserID == 3 { + staleUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) + } + require.NoError(t, user_model.LinkExternalToUser(t.Context(), staleUser, &user_model.ExternalLoginUser{ + ExternalID: tc.sub, + UserID: staleUser.ID, + LoginSourceID: authSource.ID, + Provider: authSource.Name, + })) + doOIDCSignIn(t, authSource.Name) + // external_login_user has no "id" column, so order by the primary key instead + externalLink := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: tc.sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + assert.Equal(t, correctUser.ID, externalLink.UserID) + assert.Equal(t, correctUser.Email, externalLink.Email) + assert.Equal(t, "OIDC Test User", externalLink.Name) + }) + } }) -} -func TestOIDCIgnoresStaleExternalLoginLinks(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() + // TestOAuth2CallbackReactivationGating exercises the gate in handleOAuth2SignIn: + // an inactive user can only be reactivated when who was disabled by auto-sync + t.Run("OAuth2CallbackReactivationGating", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() - setup := func(t *testing.T, sourceName, sub, userName, email string) (*auth_model.Source, *user_model.User) { - t.Helper() - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, OID: sub + "-oid", Email: email, Name: "OIDC Test User"}) + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: "test-sub", Email: "test@example.com", Name: "Test User"}) + addOAuth2Source(t, "test-oauth-source", oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "test-oauth-source") + require.NoError(t, err) + + u := &user_model.User{Name: "test-user", Email: "test@example.com"} + require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) + + extLink := &user_model.ExternalLoginUser{ + UserID: u.ID, + LoginSourceID: authSource.ID, + Provider: authSource.Name, + ExternalID: "test-sub", + } + require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, extLink)) + + prepareUserExternalLink := func(t *testing.T, accessToken, refreshToken string, expiresAt time.Time) { + err := user_model.UpdateUserCols(t.Context(), &user_model.User{ID: u.ID, IsActive: false}, "is_active") + require.NoError(t, err) + _, err = db.GetEngine(t.Context()).Where(builder.Eq{"user_id": u.ID}).Cols("access_token", "refresh_token", "expires_at"). + Update(&user_model.ExternalLoginUser{AccessToken: accessToken, RefreshToken: refreshToken, ExpiresAt: expiresAt}) + require.NoError(t, err) + require.False(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}).IsActive) + } + + t.Run("admin-disabled user is not reactivated", func(t *testing.T) { + prepareUserExternalLink(t, "an-access-token", "non-empty-refresh-token", time.Now().Add(time.Hour)) + doOIDCSignIn(t, authSource.Name) + after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) + assert.False(t, after.IsActive, "OAuth callback must not re-enable an administrator-disabled account") + }) + + t.Run("admin-disabled user without refresh token is not reactivated", func(t *testing.T) { + // GitHub / OIDC-without-offline_access sources never store a refresh token, so a + // stored access token (and no refresh token) is the normal admin-disabled state + prepareUserExternalLink(t, "an-access-token", "" /* no refresh token */, time.Now().Add(time.Hour)) + doOIDCSignIn(t, authSource.Name) + after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) + assert.False(t, after.IsActive, "OAuth callback must not re-enable an admin-disabled account on a no-refresh-token source") + }) + + t.Run("auto-sync-disabled user is reactivated", func(t *testing.T) { + // the auto-sync cron clears all three token fields when it disables a user + prepareUserExternalLink(t, "", "", time.Time{}) + doOIDCSignIn(t, authSource.Name) + after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) + assert.True(t, after.IsActive, "OAuth callback must reactivate a sync-disabled account on successful login") + }) + }) + + // TestOAuth2GroupClaimsAppliedOnFirstLogin verifies that group claims from OAuth2/OIDC + // are correctly applied to newly created users on the first login + t.Run("OAuth2GroupClaimsAppliedOnFirstLogin", func(t *testing.T) { + // Enable auto-registration to ensure first login creates user with group claims + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + // Use sub claim as username for deterministic user naming + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() + + tt := []struct { + Name string + IsAdmin bool + IsRestricted bool + SourceName string + }{ + { + Name: "user in both admin and restricted groups", + IsAdmin: true, + IsRestricted: true, + SourceName: "test-group-claims", + }, + { + Name: "no groups", + IsAdmin: false, + IsRestricted: false, + SourceName: "test-no-groups", + }, + } + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + // Set up OIDC server with group claims + srv := newFakeOIDCServer(t, FakeOIDCConfig{ + Sub: tc.SourceName, + Email: tc.SourceName + "@example.com", + Name: "Test User", + Groups: []string{"admins", "restricted-users"}, + }) + + // Ensure it's the first login so no user in database + unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) + + addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) + + doOIDCSignIn(t, tc.SourceName) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) + assert.Equal(t, tc.IsAdmin, user.IsAdmin) + assert.Equal(t, tc.IsRestricted, user.IsRestricted) + assert.Equal(t, auth_model.OAuth2, user.LoginType) + }) + } + }) + + // TestOAuth2GroupClaimsManualLinking tests that group claims are applied correctly + // when a user goes through the manual linking flow (auto-registration disabled). + t.Run("OAuth2GroupClaimsManualLinking", func(t *testing.T) { + // Disable auto-registration to force manual linking flow + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, false)() + defer test.MockVariableValue(&setting.Service.AllowOnlyInternalRegistration, false)() + + tt := []struct { + Name string + IsAdmin bool + IsRestricted bool + SourceName string + }{ + { + Name: "user in both admin and restricted groups", + IsAdmin: true, + IsRestricted: true, + SourceName: "test-group-claims-manual-linking", + }, + { + Name: "no groups", + IsAdmin: false, + IsRestricted: false, + SourceName: "test-no-groups-manual-linking", + }, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + srv := newFakeOIDCServer(t, FakeOIDCConfig{ + Sub: tc.SourceName, + Email: tc.SourceName + "@example.com", + Name: "Manual User", + Groups: []string{"admins", "restricted-users"}, + }) + addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) + unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) + session := emptyTestSession(t) + resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+tc.SourceName), http.StatusTemporaryRedirect) + + location := resp.Header().Get("Location") + u, err := url.Parse(location) + require.NoError(t, err) + state := u.Query().Get("state") + require.NotEmpty(t, state, "redirect to OIDC provider must include state") + + callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", tc.SourceName, url.QueryEscape(state)) + session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) + + // Submit the form to create a new account + linkAccountResp := session.MakeRequest(t, NewRequest(t, "GET", "/user/link_account"), http.StatusOK) + // Verify we're on the link account page + assert.Contains(t, linkAccountResp.Body.String(), "link_account") + + // Use NewRequestWithValues to POST form data (no CSRF needed in tests) + // Field names are lowercase in HTML forms: user_name, email, password, retype + req := NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{ + "user_name": tc.SourceName, + "email": tc.SourceName + "@example.com", + "password": "", // AllowOnlyExternalRegistration means no password needed + "retype": "", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) + assert.Equal(t, tc.IsAdmin, user.IsAdmin) + assert.Equal(t, tc.IsRestricted, user.IsRestricted) + assert.Equal(t, auth_model.OAuth2, user.LoginType) + }) + } + }) + + // TestOAuth2AutoLinkWithTwoFactor verifies that automatic account linking completes + // after the user passes local 2FA when an OIDC identity matches an existing account. + t.Run("OAuth2AutoLinkWithTwoFactor", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() + + const ( + sourceName = "test-oauth-auto-link-2fa" + sub = "oidc-auto-link-2fa-sub" + email = "oidc-auto-link-2fa@example.com" + userName = "oidc-auto-link-2fa" + ) + + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: email, Name: "OIDC Auto Link 2FA"}) addOAuth2Source(t, sourceName, oauth2.Source{ Provider: "openidConnect", ClientID: "test-client-id", @@ -153,109 +392,110 @@ func TestOIDCIgnoresStaleExternalLoginLinks(t *testing.T) { }) authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) require.NoError(t, err) - correctUser := &user_model.User{Name: userName, Email: email} - require.NoError(t, user_model.CreateUser(t.Context(), correctUser, &user_model.Meta{})) - return authSource, correctUser - } - // assertRelinked signs in via OIDC and asserts the stale link now points at the correct individual user. - assertRelinked := func(t *testing.T, authSource *auth_model.Source, sub string, correctUser *user_model.User) { - t.Helper() - doOIDCSignIn(t, authSource.Name) - // external_login_user has no "id" column, so order by the primary key instead + localUser := &user_model.User{Name: userName, Email: email} + require.NoError(t, user_model.CreateUser(t.Context(), localUser, &user_model.Meta{})) + + otpKey, err := totp.Generate(totp.GenerateOpts{ + SecretSize: 40, + Issuer: "gitea-test", + AccountName: localUser.Name, + }) + require.NoError(t, err) + + tfa := &auth_model.TwoFactor{UID: localUser.ID} + require.NoError(t, tfa.SetSecret(otpKey.Secret())) + require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) + + unittest.AssertNotExistsBean(t, &user_model.ExternalLoginUser{ExternalID: sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + + session := emptyTestSession(t) + resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+sourceName), http.StatusTemporaryRedirect) + + location := resp.Header().Get("Location") + u, err := url.Parse(location) + require.NoError(t, err) + state := u.Query().Get("state") + require.NotEmpty(t, state) + + callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) + resp = session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) + assert.Contains(t, resp.Header().Get("Location"), "/user/two_factor") + + session.MakeRequest(t, NewRequest(t, "GET", "/user/two_factor"), http.StatusOK) + + passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) + require.NoError(t, err) + + req := NewRequestWithValues(t, "POST", "/user/two_factor", map[string]string{ + "passcode": passcode, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + externalLink := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - assert.Equal(t, correctUser.ID, externalLink.UserID) - assert.Equal(t, correctUser.Email, externalLink.Email) - assert.Equal(t, "OIDC Test User", externalLink.Name) - } + assert.Equal(t, localUser.ID, externalLink.UserID) - t.Run("organization", func(t *testing.T) { - const sub, userName, email = "oidc-stale-org-link-sub", "guizar_m", "guizar_m@example.com" - authSource, correctUser := setup(t, "test-oidc-stale-org-link", sub, userName, email) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) - require.NoError(t, user_model.LinkExternalToUser(t.Context(), org, &user_model.ExternalLoginUser{ - ExternalID: sub, - UserID: org.ID, - LoginSourceID: authSource.ID, - Provider: authSource.Name, - })) - assertRelinked(t, authSource, sub, correctUser) + session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusOK) }) - t.Run("deleted user", func(t *testing.T) { - const sub, userName, email = "oidc-stale-deleted-link-sub", "guizar_d", "guizar_d@example.com" - const deletedUserID = 999999 - authSource, correctUser := setup(t, "test-oidc-stale-deleted", sub, userName, email) - // link the external account to a user id that does not exist, simulating a deleted user - require.NoError(t, user_model.LinkExternalToUser(t.Context(), &user_model.User{ID: deletedUserID}, &user_model.ExternalLoginUser{ - ExternalID: sub, - UserID: deletedUserID, - LoginSourceID: authSource.ID, - Provider: authSource.Name, - })) - assertRelinked(t, authSource, sub, correctUser) - }) -} + // a security key must be challenged on every path that issues a session, not just the password login + t.Run("WebAuthnSecondFactorRequired", func(t *testing.T) { + newWebAuthnUser := func(t *testing.T, name string) *user_model.User { + u := &user_model.User{Name: name, Email: name + "@example.com"} + require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) + _, err := auth_model.CreateCredential(t.Context(), u.ID, "test-key", &webauthn.Credential{ID: []byte(name)}) + require.NoError(t, err) + return u + } -// TestOAuth2CallbackReactivationGating exercises the gate in handleOAuth2SignIn: -// an inactive user can only be reactivated when who was disabled by auto-sync -func TestOAuth2CallbackReactivationGating(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() + assertOAuth2Challenged := func(t *testing.T, sourceName string) { + session := emptyTestSession(t) + resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+sourceName), http.StatusTemporaryRedirect) + u, err := url.Parse(resp.Header().Get("Location")) + require.NoError(t, err) + state := u.Query().Get("state") + require.NotEmpty(t, state) - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: "test-sub", Email: "test@example.com", Name: "Test User"}) - addOAuth2Source(t, "test-oauth-source", oauth2.Source{ - Provider: "openidConnect", - ClientID: "test-client-id", - ClientSecret: "test-client-secret", - OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", - }) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "test-oauth-source") - require.NoError(t, err) + callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) + resp = session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) + assert.Contains(t, resp.Header().Get("Location"), "/user/webauthn") + session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusSeeOther) // the redirect alone does not prove no session was issued + } - u := &user_model.User{Name: "test-user", Email: "test@example.com"} - require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) + t.Run("OAuth2AutoLink", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() - extLink := &user_model.ExternalLoginUser{ - UserID: u.ID, - LoginSourceID: authSource.ID, - Provider: authSource.Name, - ExternalID: "test-sub", - } - require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, extLink)) + const sourceName, sub = "oauth-autolink-webauthn", "autolink-sub" + u := newWebAuthnUser(t, "autolink-webauthn") + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: u.Email, Name: u.Name}) + addOAuth2Source(t, sourceName, newOIDCSource(srv, false, false)) + assertOAuth2Challenged(t, sourceName) + }) - prepareUserExternalLink := func(t *testing.T, accessToken, refreshToken string, expiresAt time.Time) { - err := user_model.UpdateUserCols(t.Context(), &user_model.User{ID: u.ID, IsActive: false}, "is_active") - require.NoError(t, err) - _, err = db.GetEngine(t.Context()).Where(builder.Eq{"user_id": u.ID}).Cols("access_token", "refresh_token", "expires_at"). - Update(&user_model.ExternalLoginUser{AccessToken: accessToken, RefreshToken: refreshToken, ExpiresAt: expiresAt}) - require.NoError(t, err) - require.False(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}).IsActive) - } + t.Run("OAuth2LinkedIdentity", func(t *testing.T) { + const sourceName, sub = "oauth-signin-webauthn", "signin-sub" + u := newWebAuthnUser(t, "signin-webauthn") + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: u.Email, Name: u.Name}) + addOAuth2Source(t, sourceName, newOIDCSource(srv, false, false)) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) + require.NoError(t, err) + require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, &user_model.ExternalLoginUser{ + ExternalID: sub, UserID: u.ID, LoginSourceID: authSource.ID, Provider: "openidConnect", + })) + assertOAuth2Challenged(t, sourceName) + }) - t.Run("admin-disabled user is not reactivated", func(t *testing.T) { - prepareUserExternalLink(t, "an-access-token", "non-empty-refresh-token", time.Now().Add(time.Hour)) - doOIDCSignIn(t, authSource.Name) - after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) - assert.False(t, after.IsActive, "OAuth callback must not re-enable an administrator-disabled account") - }) - - t.Run("admin-disabled user without refresh token is not reactivated", func(t *testing.T) { - // GitHub / OIDC-without-offline_access sources never store a refresh token, so a - // stored access token (and no refresh token) is the normal admin-disabled state - prepareUserExternalLink(t, "an-access-token", "" /* no refresh token */, time.Now().Add(time.Hour)) - doOIDCSignIn(t, authSource.Name) - after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) - assert.False(t, after.IsActive, "OAuth callback must not re-enable an admin-disabled account on a no-refresh-token source") - }) - - t.Run("auto-sync-disabled user is reactivated", func(t *testing.T) { - // the auto-sync cron clears all three token fields when it disables a user - prepareUserExternalLink(t, "", "", time.Time{}) - doOIDCSignIn(t, authSource.Name) - after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) - assert.True(t, after.IsActive, "OAuth callback must reactivate a sync-disabled account on successful login") + t.Run("PasswordReset", func(t *testing.T) { + u := newWebAuthnUser(t, "reset-webauthn") + code := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword}, u) + session := emptyTestSession(t) + req := NewRequestWithValues(t, "POST", "/user/recover_account", map[string]string{"code": code, "password": "new-Password!1"}) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Contains(t, resp.Header().Get("Location"), "/user/webauthn") + session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusSeeOther) + }) }) } @@ -377,261 +617,3 @@ func newOIDCSource(srv *httptest.Server, withAdmin, withRestricted bool) oauth2. } return src } - -// TestOAuth2GroupClaimsAppliedOnFirstLogin verifies that group claims from OAuth2/OIDC -// are correctly applied to newly created users on the first login -func TestOAuth2GroupClaimsAppliedOnFirstLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - // Enable auto-registration to ensure first login creates user with group claims - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - // Use sub claim as username for deterministic user naming - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() - - tt := []struct { - Name string - IsAdmin bool - IsRestricted bool - SourceName string - }{ - { - Name: "user in both admin and restricted groups", - IsAdmin: true, - IsRestricted: true, - SourceName: "test-group-claims", - }, - { - Name: "no groups", - IsAdmin: false, - IsRestricted: false, - SourceName: "test-no-groups", - }, - } - for _, tc := range tt { - t.Run(tc.Name, func(t *testing.T) { - // Set up OIDC server with group claims - srv := newFakeOIDCServer(t, FakeOIDCConfig{ - Sub: tc.SourceName, - Email: tc.SourceName + "@example.com", - Name: "Test User", - Groups: []string{"admins", "restricted-users"}, - }) - - // Ensure it's the first login so no user in database - unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) - - addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) - - doOIDCSignIn(t, tc.SourceName) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) - assert.Equal(t, tc.IsAdmin, user.IsAdmin) - assert.Equal(t, tc.IsRestricted, user.IsRestricted) - assert.Equal(t, auth_model.OAuth2, user.LoginType) - }) - } -} - -// TestOAuth2GroupClaimsManualLinking tests that group claims are applied correctly -// when a user goes through the manual linking flow (auto-registration disabled). -func TestOAuth2GroupClaimsManualLinking(t *testing.T) { - defer tests.PrepareTestEnv(t)() - // Disable auto-registration to force manual linking flow - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, false)() - defer test.MockVariableValue(&setting.Service.AllowOnlyInternalRegistration, false)() - - tt := []struct { - Name string - IsAdmin bool - IsRestricted bool - SourceName string - }{ - { - Name: "user in both admin and restricted groups", - IsAdmin: true, - IsRestricted: true, - SourceName: "test-group-claims-manual-linking", - }, - { - Name: "no groups", - IsAdmin: false, - IsRestricted: false, - SourceName: "test-no-groups-manual-linking", - }, - } - - for _, tc := range tt { - t.Run(tc.Name, func(t *testing.T) { - srv := newFakeOIDCServer(t, FakeOIDCConfig{ - Sub: tc.SourceName, - Email: tc.SourceName + "@example.com", - Name: "Manual User", - Groups: []string{"admins", "restricted-users"}, - }) - addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) - unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) - session := emptyTestSession(t) - resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+tc.SourceName), http.StatusTemporaryRedirect) - - location := resp.Header().Get("Location") - u, err := url.Parse(location) - require.NoError(t, err) - state := u.Query().Get("state") - require.NotEmpty(t, state, "redirect to OIDC provider must include state") - - callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", tc.SourceName, url.QueryEscape(state)) - session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) - - // Submit the form to create a new account - linkAccountResp := session.MakeRequest(t, NewRequest(t, "GET", "/user/link_account"), http.StatusOK) - // Verify we're on the link account page - assert.Contains(t, linkAccountResp.Body.String(), "link_account") - - // Use NewRequestWithValues to POST form data (no CSRF needed in tests) - // Field names are lowercase in HTML forms: user_name, email, password, retype - req := NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{ - "user_name": tc.SourceName, - "email": tc.SourceName + "@example.com", - "password": "", // AllowOnlyExternalRegistration means no password needed - "retype": "", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) - assert.Equal(t, tc.IsAdmin, user.IsAdmin) - assert.Equal(t, tc.IsRestricted, user.IsRestricted) - assert.Equal(t, auth_model.OAuth2, user.LoginType) - }) - } -} - -// TestOAuth2AutoLinkWithTwoFactor verifies that automatic account linking completes -// after the user passes local 2FA when an OIDC identity matches an existing account. -func TestOAuth2AutoLinkWithTwoFactor(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() - - const ( - sourceName = "test-oauth-auto-link-2fa" - sub = "oidc-auto-link-2fa-sub" - email = "oidc-auto-link-2fa@example.com" - userName = "oidc-auto-link-2fa" - ) - - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: email, Name: "OIDC Auto Link 2FA"}) - addOAuth2Source(t, sourceName, oauth2.Source{ - Provider: "openidConnect", - ClientID: "test-client-id", - ClientSecret: "test-client-secret", - OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", - }) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) - require.NoError(t, err) - - localUser := &user_model.User{Name: userName, Email: email} - require.NoError(t, user_model.CreateUser(t.Context(), localUser, &user_model.Meta{})) - - otpKey, err := totp.Generate(totp.GenerateOpts{ - SecretSize: 40, - Issuer: "gitea-test", - AccountName: localUser.Name, - }) - require.NoError(t, err) - - tfa := &auth_model.TwoFactor{UID: localUser.ID} - require.NoError(t, tfa.SetSecret(otpKey.Secret())) - require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) - - unittest.AssertNotExistsBean(t, &user_model.ExternalLoginUser{ExternalID: sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - - session := emptyTestSession(t) - resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+sourceName), http.StatusTemporaryRedirect) - - location := resp.Header().Get("Location") - u, err := url.Parse(location) - require.NoError(t, err) - state := u.Query().Get("state") - require.NotEmpty(t, state) - - callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) - resp = session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) - assert.Contains(t, resp.Header().Get("Location"), "/user/two_factor") - - session.MakeRequest(t, NewRequest(t, "GET", "/user/two_factor"), http.StatusOK) - - passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) - require.NoError(t, err) - - req := NewRequestWithValues(t, "POST", "/user/two_factor", map[string]string{ - "passcode": passcode, - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - externalLink := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - assert.Equal(t, localUser.ID, externalLink.UserID) - - session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusOK) -} - -// a security key must be challenged on every path that issues a session, not just the password login -func TestWebAuthnSecondFactorRequired(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - newWebAuthnUser := func(t *testing.T, name string) *user_model.User { - u := &user_model.User{Name: name, Email: name + "@example.com"} - require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) - _, err := auth_model.CreateCredential(t.Context(), u.ID, "test-key", &webauthn.Credential{ID: []byte(name)}) - require.NoError(t, err) - return u - } - - assertOAuth2Challenged := func(t *testing.T, sourceName string) { - session := emptyTestSession(t) - resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+sourceName), http.StatusTemporaryRedirect) - u, err := url.Parse(resp.Header().Get("Location")) - require.NoError(t, err) - state := u.Query().Get("state") - require.NotEmpty(t, state) - - callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) - resp = session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) - assert.Contains(t, resp.Header().Get("Location"), "/user/webauthn") - session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusSeeOther) // the redirect alone does not prove no session was issued - } - - t.Run("OAuth2AutoLink", func(t *testing.T) { - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() - - const sourceName, sub = "oauth-autolink-webauthn", "autolink-sub" - u := newWebAuthnUser(t, "autolink-webauthn") - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: u.Email, Name: u.Name}) - addOAuth2Source(t, sourceName, newOIDCSource(srv, false, false)) - assertOAuth2Challenged(t, sourceName) - }) - - t.Run("OAuth2LinkedIdentity", func(t *testing.T) { - const sourceName, sub = "oauth-signin-webauthn", "signin-sub" - u := newWebAuthnUser(t, "signin-webauthn") - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: u.Email, Name: u.Name}) - addOAuth2Source(t, sourceName, newOIDCSource(srv, false, false)) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) - require.NoError(t, err) - require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, &user_model.ExternalLoginUser{ - ExternalID: sub, UserID: u.ID, LoginSourceID: authSource.ID, Provider: "openidConnect", - })) - assertOAuth2Challenged(t, sourceName) - }) - - t.Run("PasswordReset", func(t *testing.T) { - u := newWebAuthnUser(t, "reset-webauthn") - code := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword}, u) - session := emptyTestSession(t) - req := NewRequestWithValues(t, "POST", "/user/recover_account", map[string]string{"code": code, "password": "new-Password!1"}) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Contains(t, resp.Header().Get("Location"), "/user/webauthn") - session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusSeeOther) - }) -} diff --git a/tests/integration/change_default_branch_test.go b/tests/integration/change_default_branch_test.go index f4521ed1cbe..061a2f3e028 100644 --- a/tests/integration/change_default_branch_test.go +++ b/tests/integration/change_default_branch_test.go @@ -20,23 +20,77 @@ import ( func TestChangeDefaultBranch(t *testing.T) { defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.Name) - branchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) + t.Run("ChangeDefaultBranch", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - req := NewRequestWithValues(t, "POST", branchesURL, map[string]string{ - "action": "default_branch", - "branch": "DefaultBranch", + session := loginUser(t, owner.Name) + branchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) + + req := NewRequestWithValues(t, "POST", branchesURL, map[string]string{ + "action": "default_branch", + "branch": "DefaultBranch", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequestWithValues(t, "POST", branchesURL, map[string]string{ + "action": "default_branch", + "branch": "does_not_exist", + }) + session.MakeRequest(t, req, http.StatusNotFound) }) - session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequestWithValues(t, "POST", branchesURL, map[string]string{ - "action": "default_branch", - "branch": "does_not_exist", + t.Run("Divergence", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + branchesURL := fmt.Sprintf("/%s/%s/branches", owner.Name, repo.Name) + settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) + + // check branch divergence before switching default branch + expectedBranchToDivergenceBefore := map[string]*git.DivergeObject{ + "not-signed": { + Ahead: 0, + Behind: 0, + }, + "good-sign-not-yet-validated": { + Ahead: 0, + Behind: 1, + }, + "good-sign": { + Ahead: 1, + Behind: 3, + }, + } + checkDivergence(t, session, branchesURL, "master", expectedBranchToDivergenceBefore) + + // switch default branch + newDefaultBranch := "good-sign-not-yet-validated" + req := NewRequestWithValues(t, "POST", settingsBranchesURL, map[string]string{ + "action": "default_branch", + "branch": newDefaultBranch, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // check branch divergence after switching default branch + expectedBranchToDivergenceAfter := map[string]*git.DivergeObject{ + "master": { + Ahead: 1, + Behind: 0, + }, + "not-signed": { + Ahead: 1, + Behind: 0, + }, + "good-sign": { + Ahead: 1, + Behind: 2, + }, + } + checkDivergence(t, session, branchesURL, newDefaultBranch, expectedBranchToDivergenceAfter) }) - session.MakeRequest(t, req, http.StatusNotFound) } func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]*git.DivergeObject) { @@ -77,55 +131,3 @@ func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDe assert.Equal(t, expectedCountBehind, countBehind) } } - -func TestChangeDefaultBranchDivergence(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - branchesURL := fmt.Sprintf("/%s/%s/branches", owner.Name, repo.Name) - settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) - - // check branch divergence before switching default branch - expectedBranchToDivergenceBefore := map[string]*git.DivergeObject{ - "not-signed": { - Ahead: 0, - Behind: 0, - }, - "good-sign-not-yet-validated": { - Ahead: 0, - Behind: 1, - }, - "good-sign": { - Ahead: 1, - Behind: 3, - }, - } - checkDivergence(t, session, branchesURL, "master", expectedBranchToDivergenceBefore) - - // switch default branch - newDefaultBranch := "good-sign-not-yet-validated" - req := NewRequestWithValues(t, "POST", settingsBranchesURL, map[string]string{ - "action": "default_branch", - "branch": newDefaultBranch, - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // check branch divergence after switching default branch - expectedBranchToDivergenceAfter := map[string]*git.DivergeObject{ - "master": { - Ahead: 1, - Behind: 0, - }, - "not-signed": { - Ahead: 1, - Behind: 0, - }, - "good-sign": { - Ahead: 1, - Behind: 2, - }, - } - checkDivergence(t, session, branchesURL, newDefaultBranch, expectedBranchToDivergenceAfter) -} diff --git a/tests/integration/compare_test.go b/tests/integration/compare_test.go index 8fd28dd3235..a42a3758e70 100644 --- a/tests/integration/compare_test.go +++ b/tests/integration/compare_test.go @@ -26,42 +26,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestCompareTag(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1...master") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") - // A dropdown for both base and head. - assert.Lenf(t, selection.Nodes, 2, "The template has changed") - - req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...HEAD") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - - req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...NotExisting").SetHeader("Accept", "text/html") - resp = session.MakeRequest(t, req, http.StatusNotFound) - assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - - req = NewRequest(t, "GET", "/user2/repo1/compare/invalid").SetHeader("Accept", "text/html") - resp = session.MakeRequest(t, req, http.StatusNotFound) - assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) -} - -// Compare with inferred default branch (master) -func TestCompareDefault(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") - assert.Lenf(t, selection.Nodes, 2, "The template has changed") -} - // Ensure the comparison matches what we expect func inspectCompare(t *testing.T, htmlDoc *HTMLDoc, diffCount int, diffChanges []string) { selection := htmlDoc.doc.Find("#diff-file-boxes").Children() @@ -74,154 +38,225 @@ func inspectCompare(t *testing.T, htmlDoc *HTMLDoc, diffCount int, diffChanges [ } } -// Git commit graph for repo20 -// * 8babce9 (origin/remove-files-b) Add a dummy file -// * b67e43a Delete test.csv and link_hi -// | * cfe3b3c (origin/remove-files-a) Delete test.csv and link_hi -// |/ -// * c8e31bc (origin/add-csv) Add test csv file -// * 808038d (HEAD -> master, origin/master, origin/HEAD) Added test links - -func TestCompareBranches(t *testing.T) { +func TestCompare(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") + t.Run("CompareTag", func(t *testing.T) { + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1...master") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") + // A dropdown for both base and head. + assert.Lenf(t, selection.Nodes, 2, "The template has changed") - // Indirect compare remove-files-b (head) with add-csv (base) branch - // - // 'link_hi' and 'test.csv' are deleted, 'test.txt' is added - req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) + req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...HEAD") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - diffCount := 3 - diffChanges := []string{"link_hi", "test.csv", "test.txt"} - - inspectCompare(t, htmlDoc, diffCount, diffChanges) - - // Indirect compare remove-files-b (head) with remove-files-a (base) branch - // - // 'link_hi' and 'test.csv' are deleted, 'test.txt' is added - - req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-a...remove-files-b") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - diffCount = 3 - diffChanges = []string{"link_hi", "test.csv", "test.txt"} - - inspectCompare(t, htmlDoc, diffCount, diffChanges) - - // Indirect compare remove-files-a (head) with remove-files-b (base) branch - // - // 'link_hi' and 'test.csv' are deleted - - req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b...remove-files-a") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - diffCount = 2 - diffChanges = []string{"link_hi", "test.csv"} - - inspectCompare(t, htmlDoc, diffCount, diffChanges) - - // Direct compare remove-files-b (head) with remove-files-a (base) branch - // - // 'test.txt' is deleted - - req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b..remove-files-a") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - diffCount = 1 - diffChanges = []string{"test.txt"} - - inspectCompare(t, htmlDoc, diffCount, diffChanges) - - req = NewRequest(t, "GET", "/user2/repo20/compare/8babce967f21b9dfa6987f943b91093dac58a4f0..c8e31bc7688741a5287fcde4fbb8fc129ca07027") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - inspectCompare(t, htmlDoc, 3, []string{"link_hi", "test.csv", "test.txt"}) - assert.NotContains(t, htmlDoc.doc.Text(), translation.NewLocale("en-US").TrString("repo.commits.nothing_to_compare")) - commitsHeader := strings.Join(strings.Fields(htmlDoc.doc.Find(".ui.top.attached.header .flex-text-block").First().Text()), " ") - assert.Equal(t, "0 "+translation.NewLocale("en-US").TrString("repo.commits.commits"), commitsHeader) -} - -func TestCompareWithRefSuffix(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - - // remove-files-b^ resolves to the tip's parent, so the test.txt added by the tip is excluded - req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b^") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"}) - - // a suffix resolves to a commit rather than a branch, so the page offers no pull request to create - assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) - - // the same suffix on the direct ".." comparison resolves to the same commit - req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv..remove-files-b^") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"}) - - // a ~N suffix on the base side resolves and renders the compare page, but also - // resolves to a commit rather than a branch, so no pull request form is offered - req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv~1...remove-files-b") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - htmlDoc = NewHTMLParser(t, resp.Body) - assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) - - // the web handler folds an unsupported (^{...}) and an unresolvable (~50) suffix alike into 404 - for _, basehead := range []string{ - "add-csv...remove-files-b~50", - "add-csv...remove-files-b^{/Add}", - "add-csv^{/Add}...remove-files-b", - } { - req = NewRequest(t, "GET", "/user2/repo20/compare/"+basehead).SetHeader("Accept", "text/html") + req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...NotExisting").SetHeader("Accept", "text/html") resp = session.MakeRequest(t, req, http.StatusNotFound) assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - } -} -func TestResolveRefWithSuffixContract(t *testing.T) { - defer tests.PrepareTestEnv(t)() + req = NewRequest(t, "GET", "/user2/repo1/compare/invalid").SetHeader("Accept", "text/html") + resp = session.MakeRequest(t, req, http.StatusNotFound) + assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) + }) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 31}) - gitRepo, err := git.OpenRepository(t.Context(), repo) - require.NoError(t, err) - defer gitRepo.Close() + // Compare with inferred default branch (master) + t.Run("CompareDefault", func(t *testing.T) { + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") + assert.Lenf(t, selection.Nodes, 2, "The template has changed") + }) - // a nil error guarantees a usable RefName - ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, "add-csv", "^") - require.NoError(t, err) - assert.NotEmpty(t, ref) - // a ref resolved with a suffix must be a commit SHA, not a branch ref - // (branch refs would break "New Pull Request" logic) - assert.False(t, ref.IsBranch(), "ref with suffix must not resolve to a branch") + // Git commit graph for repo20 + // * 8babce9 (origin/remove-files-b) Add a dummy file + // * b67e43a Delete test.csv and link_hi + // | * cfe3b3c (origin/remove-files-a) Delete test.csv and link_hi + // |/ + // * c8e31bc (origin/add-csv) Add test csv file + // * 808038d (HEAD -> master, origin/master, origin/HEAD) Added test links + t.Run("CompareBranches", func(t *testing.T) { + session := loginUser(t, "user2") - // a missing ref and an unresolvable suffix both report not-found instead of an empty RefName - for _, tc := range []struct{ oriRef, suffix string }{ - {"does-not-exist", ""}, - {"add-csv", "~50"}, - } { - ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, tc.oriRef, tc.suffix) - assert.ErrorIs(t, err, util.ErrNotExist, "ref %q suffix %q", tc.oriRef, tc.suffix) - assert.Empty(t, ref, "ref %q suffix %q", tc.oriRef, tc.suffix) - } -} + // Indirect compare remove-files-b (head) with add-csv (base) branch + // + // 'link_hi' and 'test.csv' are deleted, 'test.txt' is added + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) -func TestCompareBranchesNoCommonMergeBase(t *testing.T) { - defer tests.PrepareTestEnv(t)() + diffCount := 3 + diffChanges := []string{"link_hi", "test.csv", "test.txt"} - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "repo1"}) + inspectCompare(t, htmlDoc, diffCount, diffChanges) - _, _, runErr := gitcmd.NewCommand("fast-import").WithRepo(repo1).WithStdinBytes([]byte(strings.TrimSpace(` + // Indirect compare remove-files-b (head) with remove-files-a (base) branch + // + // 'link_hi' and 'test.csv' are deleted, 'test.txt' is added + + req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-a...remove-files-b") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + diffCount = 3 + diffChanges = []string{"link_hi", "test.csv", "test.txt"} + + inspectCompare(t, htmlDoc, diffCount, diffChanges) + + // Indirect compare remove-files-a (head) with remove-files-b (base) branch + // + // 'link_hi' and 'test.csv' are deleted + + req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b...remove-files-a") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + diffCount = 2 + diffChanges = []string{"link_hi", "test.csv"} + + inspectCompare(t, htmlDoc, diffCount, diffChanges) + + // Direct compare remove-files-b (head) with remove-files-a (base) branch + // + // 'test.txt' is deleted + + req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b..remove-files-a") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + diffCount = 1 + diffChanges = []string{"test.txt"} + + inspectCompare(t, htmlDoc, diffCount, diffChanges) + + req = NewRequest(t, "GET", "/user2/repo20/compare/8babce967f21b9dfa6987f943b91093dac58a4f0..c8e31bc7688741a5287fcde4fbb8fc129ca07027") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + inspectCompare(t, htmlDoc, 3, []string{"link_hi", "test.csv", "test.txt"}) + assert.NotContains(t, htmlDoc.doc.Text(), translation.NewLocale("en-US").TrString("repo.commits.nothing_to_compare")) + commitsHeader := strings.Join(strings.Fields(htmlDoc.doc.Find(".ui.top.attached.header .flex-text-block").First().Text()), " ") + assert.Equal(t, "0 "+translation.NewLocale("en-US").TrString("repo.commits.commits"), commitsHeader) + }) + + t.Run("CompareWithRefSuffix", func(t *testing.T) { + session := loginUser(t, "user2") + + // remove-files-b^ resolves to the tip's parent, so the test.txt added by the tip is excluded + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b^") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"}) + + // a suffix resolves to a commit rather than a branch, so the page offers no pull request to create + assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) + + // the same suffix on the direct ".." comparison resolves to the same commit + req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv..remove-files-b^") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"}) + + // a ~N suffix on the base side resolves and renders the compare page, but also + // resolves to a commit rather than a branch, so no pull request form is offered + req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv~1...remove-files-b") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) + htmlDoc = NewHTMLParser(t, resp.Body) + assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) + + // the web handler folds an unsupported (^{...}) and an unresolvable (~50) suffix alike into 404 + for _, basehead := range []string{ + "add-csv...remove-files-b~50", + "add-csv...remove-files-b^{/Add}", + "add-csv^{/Add}...remove-files-b", + } { + req = NewRequest(t, "GET", "/user2/repo20/compare/"+basehead).SetHeader("Accept", "text/html") + resp = session.MakeRequest(t, req, http.StatusNotFound) + assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) + } + }) + + t.Run("ResolveRefWithSuffixContract", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 31}) + gitRepo, err := git.OpenRepository(t.Context(), repo) + require.NoError(t, err) + defer gitRepo.Close() + + // a nil error guarantees a usable RefName + ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, "add-csv", "^") + require.NoError(t, err) + assert.NotEmpty(t, ref) + // a ref resolved with a suffix must be a commit SHA, not a branch ref + // (branch refs would break "New Pull Request" logic) + assert.False(t, ref.IsBranch(), "ref with suffix must not resolve to a branch") + + // a missing ref and an unresolvable suffix both report not-found instead of an empty RefName + for _, tc := range []struct{ oriRef, suffix string }{ + {"does-not-exist", ""}, + {"add-csv", "~50"}, + } { + ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, tc.oriRef, tc.suffix) + assert.ErrorIs(t, err, util.ErrNotExist, "ref %q suffix %q", tc.oriRef, tc.suffix) + assert.Empty(t, ref, "ref %q suffix %q", tc.oriRef, tc.suffix) + } + }) + + t.Run("CompareDownloadDiffOrPatch", func(t *testing.T) { + session := loginUser(t, "user2") + + t.Run("BranchToBranchDiff", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.diff") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) + assert.Contains(t, resp.Body.String(), "diff --git ") + }) + + t.Run("BranchToBranchPatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.patch") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) + assert.True(t, strings.HasPrefix(resp.Body.String(), "From "), "patch output should start with a format-patch header") + }) + + t.Run("SingleRefImplicitBase", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv.diff") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "diff --git ") + }) + + t.Run("InvalidBaseRef", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo20/compare/does-not-exist...remove-files-b.diff") + session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("PrivateRepoAnonymous", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // repo16 is private; an unauthenticated request must not leak its existence. + req := NewRequest(t, "GET", "/user2/repo16/compare/master...good-sign.diff") + MakeRequest(t, req, http.StatusNotFound) + }) + }) + + t.Run("CompareBranchesNoCommonMergeBase", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "repo1"}) + + _, _, runErr := gitcmd.NewCommand("fast-import").WithRepo(repo1).WithStdinBytes([]byte(strings.TrimSpace(` commit refs/heads/unrelated-history committer User