b3020aaffd
commit 931d6ab0e025b0268d94e455f736b09a025e0578
Merge: b34d165320d 93846eb152
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Nov 5 09:36:58 2018 +0900
Merge branch 'master-ce' into stateful_deployments
commit b34d165320d6f3298c8b776ba66270a59c217412
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Nov 2 18:07:08 2018 +0900
Fix flaky spec
commit b5e0527c5d4fe8f18b2fdda5916bae9b8cd859a4
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Nov 2 15:32:03 2018 +0900
Fix spec
commit f78a5e96e66fe2d25086df495e339b470a274df8
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Nov 2 14:59:29 2018 +0900
Remove unnecessary line in schema.rb
commit 6ce7c483e0591b5d6f9588a99853834327b80031
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Nov 2 14:55:48 2018 +0900
Add partial index for filling deployment at migration
commit aecccfb5118c8982db3ba502fdf37b5e639fbfc6
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Nov 2 14:42:24 2018 +0900
Fix fill empty finished at migration
commit 0199e1761ad1b391ae87a53a9a113d3256529e0e
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Nov 2 14:19:44 2018 +0900
Fix flaky spec
commit 56ac84cd8095afab5b909119445537b7da06a2ff
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Nov 2 10:06:49 2018 +0900
Fix guard clause to prevent multiple deployments to a job
commit 521561b6b303b54635c30cb23d78e49d14cec53d
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 20:19:24 2018 +0900
Fix spec
commit 2878da0d29b9bd2dde69a1b216203df118dd59a1
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 19:38:59 2018 +0900
Simplify the factory
commit 22fd7df02133f3a21828554965fd5619905eac2c
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 19:33:50 2018 +0900
Simplify the Deployable and BuildSuccessWorker
commit 41108959677ed614f4548443a2f4303c4c04925a
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 18:34:20 2018 +0900
Fix spec
commit ae75fe7461ac72f621498797f478d42331342b84
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 17:19:12 2018 +0900
Fix weird virtual deployment status
commit 380fee7494d06407dccc292c3cbedbcee7b6e235
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 15:59:31 2018 +0900
Fix spec
commit 29889fcbaadb3bbfd2f11c10bfbf5dceb3e3ddba
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 15:07:10 2018 +0900
Fix coding offence
commit 36ac13f345f5ef25725c2236a791a40a3a9e6126
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 14:22:17 2018 +0900
Squashed commit of the following:
commit ba9aede922e1643db3f06c56736d46d6d86d356b
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 14:21:33 2018 +0900
Fix ambiguious factory specification in update deployment service spec
commit 013afb5668cb30dc4ca5b21945c17b341e7ea7f9
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Nov 1 14:10:24 2018 +0900
Fix spec
commit 78793670d049e2dfb5fc98177eb4d10f20b9310b
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 31 18:26:12 2018 +0900
Fix spec
commit 73d27e87c66698f2e3a817bb8728f02475b7ba4f
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 31 16:22:14 2018 +0900
Fix index
commit 8580a226ea68bf5e49b35bfb5f404968bbfaf8e9
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 31 15:34:57 2018 +0900
Fix deployment relationships in Ci::Build
commit d6d28b55afd1179200b4f5188e0b53079ff3c1a7
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 31 15:27:53 2018 +0900
Fix spec
commit 94eb754e2e1bb9a1fe627f86823f571a8298d27b
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 31 14:07:11 2018 +0900
Fix spec
commit 0b30f80bcd08a7a06bdde3378ec1733f865284be
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 20:15:31 2018 +0900
Fix spec
commit 466bdcdb6af8cdb475c9fa16bd7d1dff23b11e40
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 19:28:51 2018 +0900
Fix spec
commit a7c3caac99139e70fe3f1f3d14856939fa25c527
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 17:33:47 2018 +0900
Fix factory
commit cea28ae100532e6711ce1d22676719a94e2da8a0
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 16:28:18 2018 +0900
Drop leagacy success
commit 3785d685eabc10b6597cf3db67bf08385ccf298a
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 15:37:28 2018 +0900
Remove unnecessary migration file
commit 0d597fa46eeffdbb9a4afb53005a8183e433c6bf
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 15:35:53 2018 +0900
Fix schema.rb
commit ec3c2abc6944e09f6410468ae5e356865ec7b02b
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 15:34:21 2018 +0900
Rename post migration file
commit 0e7281885a84656acf95f0f423732680f8fec076
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 15:31:01 2018 +0900
Remove include EnumWithNil
commit b3846d59c07e07275126c70361bde7f30810729e
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 15:05:50 2018 +0900
Decouple action
commit c9f9ba4eae9ca1edc7d8751e1d2e0572cb222d9c
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 14:23:29 2018 +0900
Remove status mock
commit d95bfea1ca67b3a27a3226a669c2b1266d696682
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 14:17:14 2018 +0900
Add action
commit 0cec39e0f76c22a18498f46d65ad7226fb30c3f8
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 30 13:44:07 2018 +0900
Remove unnecessary line in schema.rb
commit 7b4c5f8e1b00dd8e6aa944352f9d8a9f3ae6f1c7
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 29 19:59:41 2018 +0900
Revert build success worker
commit 0c52ffa4a23eea488c187317e8b400369846f399
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 29 19:11:47 2018 +0900
Use add_column_with_default properly
commit ba9bae357da5dfd2f6ec05f7f9db9d0b31224f48
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 29 18:40:55 2018 +0900
Fix with_status
commit 75dffc97b9c5f6fa73d9d09b125c8f849fa2caae
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 29 16:26:56 2018 +0900
Remove unnecessary line in schema.rb
commit 25188ccc52fb29ca63b9205c4d95ffc2e0afadee
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 29 16:26:17 2018 +0900
Set default values in regular migration
commit 98ea037fbf39c8d9f0db77fb50e2d08382425158
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 26 17:27:49 2018 +0900
Fix static analysis
commit e7d1765f77f9ff9b94a34985a7855bdaab1da675
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 26 16:37:10 2018 +0900
Remove empty spec
commit 0033f521ed1eae8117dba231961aa47c068bbcfb
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 26 16:34:55 2018 +0900
Simplify spec changes
commit 0be4c6b3ade6d9a8bf28bcd177c66ebd7bb7d20a
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 26 16:32:45 2018 +0900
Simplify spec changes
commit a93d25d79df7e25bdf688fc938c712922f9dc4df
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 26 16:02:31 2018 +0900
Fix flaky spec
commit 339ad50cf471ca706b29f008ccd2bb881dd5b776
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 26 15:06:22 2018 +0900
Rename Deployments Success worker
commit bd69c78085adcb9b0f8ff9b7041ae355953ad7ab
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 26 14:43:03 2018 +0900
Fix coding offence
commit 004748b2a9c5236ec13eb01289418f3d6571c92c
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 20:09:10 2018 +0900
Rename to update deployment service
commit b04a85e761de501f030f3844fd485a2b9e46f7f7
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 18:46:52 2018 +0900
Add spec for Project
commit 548af23a5a07f0c20b72849d03aa0b98a0b49134
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 18:43:25 2018 +0900
Fix spec
commit c977e4d3f17194c46a1bf857b473017ce21ef7e9
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 17:58:07 2018 +0900
Add spec for Environment
commit 73feb9010f8d8093bee4b46e56d30cfef3e8e34a
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 17:39:24 2018 +0900
Add spec for Deployment model
commit 9a3cfbf766f402571588839375cf311bb9807035
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 17:18:02 2018 +0900
Fix statis analysis
commit a30d28dbc631a29855883ca89c592a10c012f1d2
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 17:17:32 2018 +0900
Ignore nil instance
commit fa6fdd89f380e588a6bcf14b1f9aef0d14d3854b
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 16:20:40 2018 +0900
Add spec for deployable concern
commit aa91186821dc671df2c7a641e37586dd5dfc1008
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 15:37:23 2018 +0900
Clean up deployable
commit 34d3e18731f7906a3db250b105a64d1db83c2fca
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 15:13:05 2018 +0900
Fix 17 cycle analytics
commit 8dc9e00408f9b390175e7d5ea743eed4fb9e3f79
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 25 13:56:51 2018 +0900
Fix static analysys
commit 5c4175807a537bafc4b889b0a97e8f96f0e483cd
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 24 15:05:05 2018 +0900
Skip unnecessary sidekiq worker
commit 9d8b5d423f49cc247c96ce3767d03b4af305809f
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 24 14:53:13 2018 +0900
Add changelog
commit c8cabba496722240cadf7c161c80bceb09727cba
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 24 14:44:45 2018 +0900
Squashed commit of the following:
commit f7643885ac2329e18d690a4e4f2d7614b732c793
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 19:38:45 2018 +0900
Fix deployment widget specs
commit 03bd04b5c98b634dff6a0ab4292c150a9031995c
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 17:50:16 2018 +0900
Fix env status spec
commit 4a49c6502b161a12f0f62d5ec167dff777047dab
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 17:48:59 2018 +0900
Fix environment spec
commit 4044822887987e20a703990ff20352a532eeb965
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 17:47:17 2018 +0900
Fix environment spec
commit 9939d44b7eb9da371de74c0f04fed1eb3db37ad3
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 15:45:43 2018 +0900
Add a new spec for deployment success worker
commit f61c4d3657b5ef13b5da171460da68a6643ad4b5
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 15:38:11 2018 +0900
Fix cycle analytics helper
commit b6242615e8298fb7fc047c8df8006c25ad717c70
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 14:41:54 2018 +0900
Fix cycle analysis helper
commit 9a001cb4c4ed6f3b87dc612bdffc60a6b2b0a132
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 14:37:08 2018 +0900
Ignore coding offence in build success worker's spec
commit 1fb88583025bac8a56172cbd59be04258ea4c5f3
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 23 14:33:11 2018 +0900
Added more spec for deployments
commit 1a6ba97ababbf62e8dd0ae0c56d75ab1268fd0ce
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 19:36:50 2018 +0900
Move after create hookd into success worker
commit 09de5fed5d6f108423779cf9d9e7f1d21f3c1c91
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 19:30:06 2018 +0900
Fix build spec
commit 73a55cbcabbb1e928eca3e53e8ff75dec178bc90
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 19:08:43 2018 +0900
Fix update_deployment_metrics_service_spec.rb
commit ee05136a02ae9fa348b4b89b9a69937ebb9697dd
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 17:32:05 2018 +0900
Remove unnecessary degelate
commit e246ddeebc01a807ccc36fdb484c3e72ad91e680
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 16:07:39 2018 +0900
Remove unnecessary optimistic locking
commit dcc225c8237b90e3bc8dcc3dc2e3252e0b0be093
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 16:00:22 2018 +0900
Simplify status replication
commit 13a5fd7afb67ba2712fcaecaea5fedf05f9ad177
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 15:29:24 2018 +0900
Fix sidekiq queue names
commit dcc796f48d523538e1c91b9cd3e1c7065e5329b1
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 15:23:55 2018 +0900
Revert success check in update_merge_request_metrics
commit 129ef083d637d4acb8c97a6d9ab96deb2ff6efcd
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 15:18:31 2018 +0900
Fix queue name of deployment success worker
commit 10fe5a6484f4f02322ce5bb16844fc7b1d565963
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 15:09:42 2018 +0900
Introduce deployable module
commit d91260bbe105bf46f6c06d9e9593c8c4cd5139cf
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 14:05:31 2018 +0900
Add database index for successful deployments
commit 74274147263de4b60870065a19935498ce662e30
Author: Shinya Maeda <shinya@gitlab.com>
Date: Mon Oct 22 13:51:59 2018 +0900
Fix invalid state transition
commit ff18463cc847bf3cf5a3e49f3651eedfdf67c7e6
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 19 20:05:15 2018 +0900
Fix coding style offence
commit 0202c0f5b631601edab7b359b087b307f5eb7ba3
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 19 18:34:07 2018 +0900
Target only successful deployments from other relations
commit 1f2758cb030dec1df5dda30f6bc3e25b6d0841c9
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 19 18:21:28 2018 +0900
Add namespace explicitly
commit 3d9227b6e5642cecde88d4edac925125f6474b11
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 19 17:42:30 2018 +0900
Fix spec in DeleteInconsistentInternalIdRecords
commit 3e0cc99ff6c5c7188511618228a6ec027752ce69
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 19 16:10:16 2018 +0900
Fixed spec
commit 8de09b8bb31f7b9f24ecdf9f2dd8ef358a260263
Author: Shinya Maeda <shinya@gitlab.com>
Date: Fri Oct 19 14:22:35 2018 +0900
Fix create deployment service
commit 31957570b4444492eeb412e765f96a56416c25f3
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 18 20:21:26 2018 +0900
Move CreateDeploymentService. Fix Cycle analytics spec and fixture.
commit d2eb433a1bb9710c0d4778c4f34c12b6b64f60e6
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 18 20:11:22 2018 +0900
Fix build success worker
commit 25e6cd87138bcdb69de8785ca367e479c8dbcc59
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 18 19:49:13 2018 +0900
Fix create deployment service spec
commit d268bf410bf65e86c81eb76d50aa8e145b32d249
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 18 19:01:23 2018 +0900
Fix cycle analysys spec's deployment
commit 525ade8aa1e4394ed8a759bb0437e407fbe74a35
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 18 18:24:04 2018 +0900
Fix factory to set legacy status by default
commit c6a990821ac0a1ffa49e20e2d78d94b8ce075914
Author: Shinya Maeda <shinya@gitlab.com>
Date: Thu Oct 18 17:25:40 2018 +0900
Remove unnecessary lib from deployment
commit a6107e0e85ac26ee09da3316ebc11de32f067d82
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 17 17:38:58 2018 +0900
Fix recursive call
commit 15c5f3b64061a75af3c3039ca7f49b1cc4ff3068
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 17 17:30:44 2018 +0900
Add finished_at
commit c8d3d70366f694d78acb7e30d342c7697798b922
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 17 15:55:31 2018 +0900
Fix last_deployment methods as it used to return successful deployment always
commit 96bbe8670cece021766fde95fe573cbbe23d1e55
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 17 15:49:57 2018 +0900
Redefine statuses
commit c86a9d0bd2ab3e7a00bf61f094a96ee99b76b289
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 17 14:50:27 2018 +0900
Fix schema.rb
commit 9ff5f0eaafbc08795018c7bb282b19f6327dee21
Author: Shinya Maeda <shinya@gitlab.com>
Date: Wed Oct 17 14:18:04 2018 +0900
Default status nil to success
commit 5928bd9bb94e1e8908ed1561e01595be84d5f4ec
Author: Shinya Maeda <shinya@gitlab.com>
Date: Tue Oct 16 15:13:48 2018 +0900
Add status to Deployment
4016 lines
122 KiB
Ruby
4016 lines
122 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe Project do
|
|
include ProjectForksHelper
|
|
include GitHelpers
|
|
|
|
describe 'associations' do
|
|
it { is_expected.to belong_to(:group) }
|
|
it { is_expected.to belong_to(:namespace) }
|
|
it { is_expected.to belong_to(:creator).class_name('User') }
|
|
it { is_expected.to belong_to(:pool_repository) }
|
|
it { is_expected.to have_many(:users) }
|
|
it { is_expected.to have_many(:services) }
|
|
it { is_expected.to have_many(:events) }
|
|
it { is_expected.to have_many(:merge_requests) }
|
|
it { is_expected.to have_many(:issues) }
|
|
it { is_expected.to have_many(:milestones) }
|
|
it { is_expected.to have_many(:project_members).dependent(:delete_all) }
|
|
it { is_expected.to have_many(:users).through(:project_members) }
|
|
it { is_expected.to have_many(:requesters).dependent(:delete_all) }
|
|
it { is_expected.to have_many(:notes) }
|
|
it { is_expected.to have_many(:snippets).class_name('ProjectSnippet') }
|
|
it { is_expected.to have_many(:deploy_keys_projects) }
|
|
it { is_expected.to have_many(:deploy_keys) }
|
|
it { is_expected.to have_many(:hooks) }
|
|
it { is_expected.to have_many(:protected_branches) }
|
|
it { is_expected.to have_one(:slack_service) }
|
|
it { is_expected.to have_one(:microsoft_teams_service) }
|
|
it { is_expected.to have_one(:mattermost_service) }
|
|
it { is_expected.to have_one(:hangouts_chat_service) }
|
|
it { is_expected.to have_one(:packagist_service) }
|
|
it { is_expected.to have_one(:pushover_service) }
|
|
it { is_expected.to have_one(:asana_service) }
|
|
it { is_expected.to have_many(:boards) }
|
|
it { is_expected.to have_one(:campfire_service) }
|
|
it { is_expected.to have_one(:drone_ci_service) }
|
|
it { is_expected.to have_one(:emails_on_push_service) }
|
|
it { is_expected.to have_one(:pipelines_email_service) }
|
|
it { is_expected.to have_one(:irker_service) }
|
|
it { is_expected.to have_one(:pivotaltracker_service) }
|
|
it { is_expected.to have_one(:hipchat_service) }
|
|
it { is_expected.to have_one(:flowdock_service) }
|
|
it { is_expected.to have_one(:assembla_service) }
|
|
it { is_expected.to have_one(:slack_slash_commands_service) }
|
|
it { is_expected.to have_one(:mattermost_slash_commands_service) }
|
|
it { is_expected.to have_one(:buildkite_service) }
|
|
it { is_expected.to have_one(:bamboo_service) }
|
|
it { is_expected.to have_one(:teamcity_service) }
|
|
it { is_expected.to have_one(:jira_service) }
|
|
it { is_expected.to have_one(:redmine_service) }
|
|
it { is_expected.to have_one(:custom_issue_tracker_service) }
|
|
it { is_expected.to have_one(:bugzilla_service) }
|
|
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
|
|
it { is_expected.to have_one(:external_wiki_service) }
|
|
it { is_expected.to have_one(:project_feature) }
|
|
it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
|
|
it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
|
|
it { is_expected.to have_one(:last_event).class_name('Event') }
|
|
it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
|
|
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
|
|
it { is_expected.to have_many(:commit_statuses) }
|
|
it { is_expected.to have_many(:pipelines) }
|
|
it { is_expected.to have_many(:builds) }
|
|
it { is_expected.to have_many(:build_trace_section_names)}
|
|
it { is_expected.to have_many(:runner_projects) }
|
|
it { is_expected.to have_many(:runners) }
|
|
it { is_expected.to have_many(:variables) }
|
|
it { is_expected.to have_many(:triggers) }
|
|
it { is_expected.to have_many(:pages_domains) }
|
|
it { is_expected.to have_many(:labels).class_name('ProjectLabel') }
|
|
it { is_expected.to have_many(:users_star_projects) }
|
|
it { is_expected.to have_many(:repository_languages) }
|
|
it { is_expected.to have_many(:environments) }
|
|
it { is_expected.to have_many(:deployments) }
|
|
it { is_expected.to have_many(:todos) }
|
|
it { is_expected.to have_many(:releases) }
|
|
it { is_expected.to have_many(:lfs_objects_projects) }
|
|
it { is_expected.to have_many(:project_group_links) }
|
|
it { is_expected.to have_many(:notification_settings).dependent(:delete_all) }
|
|
it { is_expected.to have_many(:forked_to_members).class_name('ForkNetworkMember') }
|
|
it { is_expected.to have_many(:forks).through(:forked_to_members) }
|
|
it { is_expected.to have_many(:uploads) }
|
|
it { is_expected.to have_many(:pipeline_schedules) }
|
|
it { is_expected.to have_many(:members_and_requesters) }
|
|
it { is_expected.to have_many(:clusters) }
|
|
it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
|
|
it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
|
|
it { is_expected.to have_many(:lfs_file_locks) }
|
|
it { is_expected.to have_many(:project_deploy_tokens) }
|
|
it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
|
|
|
|
it 'has an inverse relationship with merge requests' do
|
|
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
|
|
end
|
|
|
|
context 'after initialized' do
|
|
it "has a project_feature" do
|
|
expect(described_class.new.project_feature).to be_present
|
|
end
|
|
end
|
|
|
|
context 'when creating a new project' do
|
|
it 'automatically creates a CI/CD settings row' do
|
|
project = create(:project)
|
|
|
|
expect(project.ci_cd_settings).to be_an_instance_of(ProjectCiCdSetting)
|
|
expect(project.ci_cd_settings).to be_persisted
|
|
end
|
|
end
|
|
|
|
context 'Site Statistics' do
|
|
context 'when creating a new project' do
|
|
it 'tracks project in SiteStatistic' do
|
|
expect { create(:project) }.to change { SiteStatistic.fetch.repositories_count }.by(1)
|
|
end
|
|
end
|
|
|
|
context 'when deleting a project' do
|
|
it 'untracks project in SiteStatistic' do
|
|
project = create(:project)
|
|
|
|
expect { project.destroy }.to change { SiteStatistic.fetch.repositories_count }.by(-1)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'updating cd_cd_settings' do
|
|
it 'does not raise an error' do
|
|
project = create(:project)
|
|
|
|
expect { project.update(ci_cd_settings: nil) }.not_to raise_exception
|
|
end
|
|
end
|
|
|
|
describe '#members & #requesters' do
|
|
let(:project) { create(:project, :public, :access_requestable) }
|
|
let(:requester) { create(:user) }
|
|
let(:developer) { create(:user) }
|
|
before do
|
|
project.request_access(requester)
|
|
project.add_developer(developer)
|
|
end
|
|
|
|
it_behaves_like 'members and requesters associations' do
|
|
let(:namespace) { project }
|
|
end
|
|
end
|
|
|
|
describe '#boards' do
|
|
it 'raises an error when attempting to add more than one board to the project' do
|
|
subject.boards.build
|
|
|
|
expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
|
|
expect(subject.boards.size).to eq 1
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'modules' do
|
|
subject { described_class }
|
|
|
|
it { is_expected.to include_module(Gitlab::ConfigHelper) }
|
|
it { is_expected.to include_module(Gitlab::ShellAdapter) }
|
|
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
|
|
it { is_expected.to include_module(Referable) }
|
|
it { is_expected.to include_module(Sortable) }
|
|
end
|
|
|
|
describe 'validation' do
|
|
let!(:project) { create(:project) }
|
|
|
|
it { is_expected.to validate_presence_of(:name) }
|
|
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
|
|
it { is_expected.to validate_length_of(:name).is_at_most(255) }
|
|
it { is_expected.to validate_presence_of(:path) }
|
|
it { is_expected.to validate_length_of(:path).is_at_most(255) }
|
|
it { is_expected.to validate_length_of(:description).is_at_most(2000) }
|
|
it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) }
|
|
it { is_expected.to allow_value('').for(:ci_config_path) }
|
|
it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) }
|
|
it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) }
|
|
it { is_expected.to validate_presence_of(:creator) }
|
|
it { is_expected.to validate_presence_of(:namespace) }
|
|
it { is_expected.to validate_presence_of(:repository_storage) }
|
|
|
|
it 'validates build timeout constraints' do
|
|
is_expected.to validate_numericality_of(:build_timeout)
|
|
.only_integer
|
|
.is_greater_than_or_equal_to(10.minutes)
|
|
.is_less_than(1.month)
|
|
.with_message('needs to be beetween 10 minutes and 1 month')
|
|
end
|
|
|
|
it 'does not allow new projects beyond user limits' do
|
|
project2 = build(:project)
|
|
allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
|
|
expect(project2).not_to be_valid
|
|
expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
|
|
end
|
|
|
|
describe 'wiki path conflict' do
|
|
context "when the new path has been used by the wiki of other Project" do
|
|
it 'has an error on the name attribute' do
|
|
new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
|
|
|
|
expect(new_project).not_to be_valid
|
|
expect(new_project.errors[:name].first).to eq('has already been taken')
|
|
end
|
|
end
|
|
|
|
context "when the new wiki path has been used by the path of other Project" do
|
|
it 'has an error on the name attribute' do
|
|
project_with_wiki_suffix = create(:project, path: 'foo.wiki')
|
|
new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
|
|
|
|
expect(new_project).not_to be_valid
|
|
expect(new_project.errors[:name].first).to eq('has already been taken')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'repository storages inclusion' do
|
|
let(:project2) { build(:project, repository_storage: 'missing') }
|
|
|
|
before do
|
|
storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
|
end
|
|
|
|
it "does not allow repository storages that don't match a label in the configuration" do
|
|
expect(project2).not_to be_valid
|
|
expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
|
|
end
|
|
end
|
|
|
|
it 'does not allow an invalid URI as import_url' do
|
|
project = build(:project, import_url: 'invalid://')
|
|
|
|
expect(project).not_to be_valid
|
|
end
|
|
|
|
it 'does allow a SSH URI as import_url for persisted projects' do
|
|
project = create(:project)
|
|
project.import_url = 'ssh://test@gitlab.com/project.git'
|
|
|
|
expect(project).to be_valid
|
|
end
|
|
|
|
it 'does not allow a SSH URI as import_url for new projects' do
|
|
project = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
|
|
|
|
expect(project).not_to be_valid
|
|
end
|
|
|
|
it 'does allow a valid URI as import_url' do
|
|
project = build(:project, import_url: 'http://gitlab.com/project.git')
|
|
|
|
expect(project).to be_valid
|
|
end
|
|
|
|
it 'allows an empty URI' do
|
|
project = build(:project, import_url: '')
|
|
|
|
expect(project).to be_valid
|
|
end
|
|
|
|
it 'does not produce import data on an empty URI' do
|
|
project = build(:project, import_url: '')
|
|
|
|
expect(project.import_data).to be_nil
|
|
end
|
|
|
|
it 'does not produce import data on an invalid URI' do
|
|
project = build(:project, import_url: 'test://')
|
|
|
|
expect(project.import_data).to be_nil
|
|
end
|
|
|
|
it "does not allow import_url pointing to localhost" do
|
|
project = build(:project, import_url: 'http://localhost:9000/t.git')
|
|
|
|
expect(project).to be_invalid
|
|
expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed')
|
|
end
|
|
|
|
it "does not allow import_url with invalid ports for new projects" do
|
|
project = build(:project, import_url: 'http://github.com:25/t.git')
|
|
|
|
expect(project).to be_invalid
|
|
expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
|
|
end
|
|
|
|
it "does not allow import_url with invalid ports for persisted projects" do
|
|
project = create(:project)
|
|
project.import_url = 'http://github.com:25/t.git'
|
|
|
|
expect(project).to be_invalid
|
|
expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
|
|
end
|
|
|
|
it "does not allow import_url with invalid user" do
|
|
project = build(:project, import_url: 'http://$user:password@github.com/t.git')
|
|
|
|
expect(project).to be_invalid
|
|
expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
|
|
end
|
|
|
|
describe 'project pending deletion' do
|
|
let!(:project_pending_deletion) do
|
|
create(:project,
|
|
pending_delete: true)
|
|
end
|
|
let(:new_project) do
|
|
build(:project,
|
|
name: project_pending_deletion.name,
|
|
namespace: project_pending_deletion.namespace)
|
|
end
|
|
|
|
before do
|
|
new_project.validate
|
|
end
|
|
|
|
it 'contains errors related to the project being deleted' do
|
|
expect(new_project.errors.full_messages.first).to eq('The project is still being deleted. Please try again later.')
|
|
end
|
|
end
|
|
|
|
describe 'path validation' do
|
|
it 'allows paths reserved on the root namespace' do
|
|
project = build(:project, path: 'api')
|
|
|
|
expect(project).to be_valid
|
|
end
|
|
|
|
it 'rejects paths reserved on another level' do
|
|
project = build(:project, path: 'tree')
|
|
|
|
expect(project).not_to be_valid
|
|
end
|
|
|
|
it 'rejects nested paths' do
|
|
parent = create(:group, :nested, path: 'environments')
|
|
project = build(:project, path: 'folders', namespace: parent)
|
|
|
|
expect(project).not_to be_valid
|
|
end
|
|
|
|
it 'allows a reserved group name' do
|
|
parent = create(:group)
|
|
project = build(:project, path: 'avatar', namespace: parent)
|
|
|
|
expect(project).to be_valid
|
|
end
|
|
|
|
it 'allows a path ending in a period' do
|
|
project = build(:project, path: 'foo.')
|
|
|
|
expect(project).to be_valid
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'project token' do
|
|
it 'sets an random token if none provided' do
|
|
project = FactoryBot.create(:project, runners_token: '')
|
|
expect(project.runners_token).not_to eq('')
|
|
end
|
|
|
|
it 'does not set an random token if one provided' do
|
|
project = FactoryBot.create(:project, runners_token: 'my-token')
|
|
expect(project.runners_token).to eq('my-token')
|
|
end
|
|
end
|
|
|
|
describe 'Respond to' do
|
|
it { is_expected.to respond_to(:url_to_repo) }
|
|
it { is_expected.to respond_to(:repo_exists?) }
|
|
it { is_expected.to respond_to(:execute_hooks) }
|
|
it { is_expected.to respond_to(:owner) }
|
|
it { is_expected.to respond_to(:path_with_namespace) }
|
|
it { is_expected.to respond_to(:full_path) }
|
|
end
|
|
|
|
describe 'delegation' do
|
|
[:add_guest, :add_reporter, :add_developer, :add_maintainer, :add_user, :add_users].each do |method|
|
|
it { is_expected.to delegate_method(method).to(:team) }
|
|
end
|
|
|
|
it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
|
|
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
|
|
end
|
|
|
|
describe '#to_reference_with_postfix' do
|
|
it 'returns the full path with reference_postfix' do
|
|
namespace = create(:namespace, path: 'sample-namespace')
|
|
project = create(:project, path: 'sample-project', namespace: namespace)
|
|
|
|
expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>'
|
|
end
|
|
end
|
|
|
|
describe '#to_reference' do
|
|
let(:owner) { create(:user, name: 'Gitlab') }
|
|
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
|
|
let(:project) { create(:project, path: 'sample-project', namespace: namespace) }
|
|
let(:group) { create(:group, name: 'Group', path: 'sample-group') }
|
|
|
|
context 'when nil argument' do
|
|
it 'returns nil' do
|
|
expect(project.to_reference).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'when full is true' do
|
|
it 'returns complete path to the project' do
|
|
expect(project.to_reference(full: true)).to eq 'sample-namespace/sample-project'
|
|
expect(project.to_reference(project, full: true)).to eq 'sample-namespace/sample-project'
|
|
expect(project.to_reference(group, full: true)).to eq 'sample-namespace/sample-project'
|
|
end
|
|
end
|
|
|
|
context 'when same project argument' do
|
|
it 'returns nil' do
|
|
expect(project.to_reference(project)).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'when cross namespace project argument' do
|
|
let(:another_namespace_project) { create(:project, name: 'another-project') }
|
|
|
|
it 'returns complete path to the project' do
|
|
expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project'
|
|
end
|
|
end
|
|
|
|
context 'when same namespace / cross-project argument' do
|
|
let(:another_project) { create(:project, namespace: namespace) }
|
|
|
|
it 'returns path to the project' do
|
|
expect(project.to_reference(another_project)).to eq 'sample-project'
|
|
end
|
|
end
|
|
|
|
context 'when different namespace / cross-project argument' do
|
|
let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
|
|
let(:another_project) { create(:project, path: 'another-project', namespace: another_namespace) }
|
|
|
|
it 'returns full path to the project' do
|
|
expect(project.to_reference(another_project)).to eq 'sample-namespace/sample-project'
|
|
end
|
|
end
|
|
|
|
context 'when argument is a namespace' do
|
|
context 'with same project path' do
|
|
it 'returns path to the project' do
|
|
expect(project.to_reference(namespace)).to eq 'sample-project'
|
|
end
|
|
end
|
|
|
|
context 'with different project path' do
|
|
it 'returns full path to the project' do
|
|
expect(project.to_reference(group)).to eq 'sample-namespace/sample-project'
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#to_human_reference' do
|
|
let(:owner) { create(:user, name: 'Gitlab') }
|
|
let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) }
|
|
let(:project) { create(:project, name: 'Sample project', namespace: namespace) }
|
|
|
|
context 'when nil argument' do
|
|
it 'returns nil' do
|
|
expect(project.to_human_reference).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'when same project argument' do
|
|
it 'returns nil' do
|
|
expect(project.to_human_reference(project)).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'when cross namespace project argument' do
|
|
let(:another_namespace_project) { create(:project, name: 'another-project') }
|
|
|
|
it 'returns complete name with namespace of the project' do
|
|
expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project'
|
|
end
|
|
end
|
|
|
|
context 'when same namespace / cross-project argument' do
|
|
let(:another_project) { create(:project, namespace: namespace) }
|
|
|
|
it 'returns name of the project' do
|
|
expect(project.to_human_reference(another_project)).to eq 'Sample project'
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#merge_method' do
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
where(:ff, :rebase, :method) do
|
|
true | true | :ff
|
|
true | false | :ff
|
|
false | true | :rebase_merge
|
|
false | false | :merge
|
|
end
|
|
|
|
with_them do
|
|
let(:project) { build(:project, merge_requests_rebase_enabled: rebase, merge_requests_ff_only_enabled: ff) }
|
|
|
|
subject { project.merge_method }
|
|
|
|
it { is_expected.to eq(method) }
|
|
end
|
|
end
|
|
|
|
it 'returns valid url to repo' do
|
|
project = described_class.new(path: 'somewhere')
|
|
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
|
|
end
|
|
|
|
describe "#web_url" do
|
|
let(:project) { create(:project, path: "somewhere") }
|
|
|
|
it 'returns the full web URL for this repo' do
|
|
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere")
|
|
end
|
|
end
|
|
|
|
describe "#readme_url" do
|
|
context 'with a non-existing repository' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'returns nil' do
|
|
expect(project.readme_url).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'with an existing repository' do
|
|
context 'when no README exists' do
|
|
let(:project) { create(:project, :empty_repo) }
|
|
|
|
it 'returns nil' do
|
|
expect(project.readme_url).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'when a README exists' do
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
it 'returns the README' do
|
|
expect(project.readme_url).to eq("#{project.web_url}/blob/master/README.md")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#new_issuable_address" do
|
|
let(:project) { create(:project, path: "somewhere") }
|
|
let(:user) { create(:user) }
|
|
|
|
context 'incoming email enabled' do
|
|
before do
|
|
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
|
|
end
|
|
|
|
it 'returns the address to create a new issue' do
|
|
address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
|
|
|
|
expect(project.new_issuable_address(user, 'issue')).to eq(address)
|
|
end
|
|
|
|
it 'returns the address to create a new merge request' do
|
|
address = "p+#{project.full_path}+merge-request+#{user.incoming_email_token}@gl.ab"
|
|
|
|
expect(project.new_issuable_address(user, 'merge_request')).to eq(address)
|
|
end
|
|
end
|
|
|
|
context 'incoming email disabled' do
|
|
before do
|
|
stub_incoming_email_setting(enabled: false)
|
|
end
|
|
|
|
it 'returns nil' do
|
|
expect(project.new_issuable_address(user, 'issue')).to be_nil
|
|
end
|
|
|
|
it 'returns nil' do
|
|
expect(project.new_issuable_address(user, 'merge_request')).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'last_activity methods' do
|
|
let(:timestamp) { 2.hours.ago }
|
|
# last_activity_at gets set to created_at upon creation
|
|
let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
|
|
|
|
describe 'last_activity' do
|
|
it 'alias last_activity to last_event' do
|
|
last_event = create(:event, :closed, project: project)
|
|
|
|
expect(project.last_activity).to eq(last_event)
|
|
end
|
|
end
|
|
|
|
describe 'last_activity_date' do
|
|
it 'returns the creation date of the project\'s last event if present' do
|
|
new_event = create(:event, :closed, project: project, created_at: Time.now)
|
|
|
|
project.reload
|
|
expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
|
|
end
|
|
|
|
it 'returns the project\'s last update date if it has no events' do
|
|
expect(project.last_activity_date).to eq(project.updated_at)
|
|
end
|
|
|
|
it 'returns the most recent timestamp' do
|
|
project.update(updated_at: nil,
|
|
last_activity_at: timestamp,
|
|
last_repository_updated_at: timestamp - 1.hour)
|
|
|
|
expect(project.last_activity_date).to be_like_time(timestamp)
|
|
|
|
project.update(updated_at: timestamp,
|
|
last_activity_at: timestamp - 1.hour,
|
|
last_repository_updated_at: nil)
|
|
|
|
expect(project.last_activity_date).to be_like_time(timestamp)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#get_issue' do
|
|
let(:project) { create(:project) }
|
|
let!(:issue) { create(:issue, project: project) }
|
|
let(:user) { create(:user) }
|
|
|
|
before do
|
|
project.add_developer(user)
|
|
end
|
|
|
|
context 'with default issues tracker' do
|
|
it 'returns an issue' do
|
|
expect(project.get_issue(issue.iid, user)).to eq issue
|
|
end
|
|
|
|
it 'returns count of open issues' do
|
|
expect(project.open_issues_count).to eq(1)
|
|
end
|
|
|
|
it 'returns nil when no issue found' do
|
|
expect(project.get_issue(999, user)).to be_nil
|
|
end
|
|
|
|
it "returns nil when user doesn't have access" do
|
|
user = create(:user)
|
|
expect(project.get_issue(issue.iid, user)).to eq nil
|
|
end
|
|
end
|
|
|
|
context 'with external issues tracker' do
|
|
let!(:internal_issue) { create(:issue, project: project) }
|
|
before do
|
|
allow(project).to receive(:external_issue_tracker).and_return(true)
|
|
end
|
|
|
|
context 'when internal issues are enabled' do
|
|
it 'returns interlan issue' do
|
|
issue = project.get_issue(internal_issue.iid, user)
|
|
|
|
expect(issue).to be_kind_of(Issue)
|
|
expect(issue.iid).to eq(internal_issue.iid)
|
|
expect(issue.project).to eq(project)
|
|
end
|
|
|
|
it 'returns an ExternalIssue when internal issue does not exists' do
|
|
issue = project.get_issue('FOO-1234', user)
|
|
|
|
expect(issue).to be_kind_of(ExternalIssue)
|
|
expect(issue.iid).to eq('FOO-1234')
|
|
expect(issue.project).to eq(project)
|
|
end
|
|
end
|
|
|
|
context 'when internal issues are disabled' do
|
|
before do
|
|
project.issues_enabled = false
|
|
project.save!
|
|
end
|
|
|
|
it 'returns always an External issues' do
|
|
issue = project.get_issue(internal_issue.iid, user)
|
|
expect(issue).to be_kind_of(ExternalIssue)
|
|
expect(issue.iid).to eq(internal_issue.iid.to_s)
|
|
expect(issue.project).to eq(project)
|
|
end
|
|
|
|
it 'returns an ExternalIssue when internal issue does not exists' do
|
|
issue = project.get_issue('FOO-1234', user)
|
|
expect(issue).to be_kind_of(ExternalIssue)
|
|
expect(issue.iid).to eq('FOO-1234')
|
|
expect(issue.project).to eq(project)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#issue_exists?' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'is truthy when issue exists' do
|
|
expect(project).to receive(:get_issue).and_return(double)
|
|
expect(project.issue_exists?(1)).to be_truthy
|
|
end
|
|
|
|
it 'is falsey when issue does not exist' do
|
|
expect(project).to receive(:get_issue).and_return(nil)
|
|
expect(project.issue_exists?(1)).to be_falsey
|
|
end
|
|
end
|
|
|
|
describe '#to_param' do
|
|
context 'with namespace' do
|
|
before do
|
|
@group = create(:group, name: 'gitlab')
|
|
@project = create(:project, name: 'gitlabhq', namespace: @group)
|
|
end
|
|
|
|
it { expect(@project.to_param).to eq('gitlabhq') }
|
|
end
|
|
|
|
context 'with invalid path' do
|
|
it 'returns previous path to keep project suitable for use in URLs when persisted' do
|
|
project = create(:project, path: 'gitlab')
|
|
project.path = 'foo&bar'
|
|
|
|
expect(project).not_to be_valid
|
|
expect(project.to_param).to eq 'gitlab'
|
|
end
|
|
|
|
it 'returns current path when new record' do
|
|
project = build(:project, path: 'gitlab')
|
|
project.path = 'foo&bar'
|
|
|
|
expect(project).not_to be_valid
|
|
expect(project.to_param).to eq 'foo&bar'
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#repository' do
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
it 'returns valid repo' do
|
|
expect(project.repository).to be_kind_of(Repository)
|
|
end
|
|
end
|
|
|
|
describe '#default_issues_tracker?' do
|
|
it "is true if used internal tracker" do
|
|
project = build(:project)
|
|
|
|
expect(project.default_issues_tracker?).to be_truthy
|
|
end
|
|
|
|
it "is false if used other tracker" do
|
|
# NOTE: The current nature of this factory requires persistence
|
|
project = create(:redmine_project)
|
|
|
|
expect(project.default_issues_tracker?).to be_falsey
|
|
end
|
|
end
|
|
|
|
describe '#empty_repo?' do
|
|
context 'when the repo does not exist' do
|
|
let(:project) { build_stubbed(:project) }
|
|
|
|
it 'returns true' do
|
|
expect(project.empty_repo?).to be(true)
|
|
end
|
|
end
|
|
|
|
context 'when the repo exists' do
|
|
let(:project) { create(:project, :repository) }
|
|
let(:empty_project) { create(:project, :empty_repo) }
|
|
|
|
it { expect(empty_project.empty_repo?).to be(true) }
|
|
it { expect(project.empty_repo?).to be(false) }
|
|
end
|
|
end
|
|
|
|
describe '#external_issue_tracker' do
|
|
let(:project) { create(:project) }
|
|
let(:ext_project) { create(:redmine_project) }
|
|
|
|
context 'on existing projects with no value for has_external_issue_tracker' do
|
|
before do
|
|
project.update_column(:has_external_issue_tracker, nil)
|
|
ext_project.update_column(:has_external_issue_tracker, nil)
|
|
end
|
|
|
|
it 'updates the has_external_issue_tracker boolean' do
|
|
expect do
|
|
project.external_issue_tracker
|
|
end.to change { project.reload.has_external_issue_tracker }.to(false)
|
|
|
|
expect do
|
|
ext_project.external_issue_tracker
|
|
end.to change { ext_project.reload.has_external_issue_tracker }.to(true)
|
|
end
|
|
end
|
|
|
|
it 'returns nil and does not query services when there is no external issue tracker' do
|
|
expect(project).not_to receive(:services)
|
|
|
|
expect(project.external_issue_tracker).to eq(nil)
|
|
end
|
|
|
|
it 'retrieves external_issue_tracker querying services and cache it when there is external issue tracker' do
|
|
ext_project.reload # Factory returns a project with changed attributes
|
|
expect(ext_project).to receive(:services).once.and_call_original
|
|
|
|
2.times { expect(ext_project.external_issue_tracker).to be_a_kind_of(RedmineService) }
|
|
end
|
|
end
|
|
|
|
describe '#cache_has_external_issue_tracker' do
|
|
let(:project) { create(:project, has_external_issue_tracker: nil) }
|
|
|
|
it 'stores true if there is any external_issue_tracker' do
|
|
services = double(:service, external_issue_trackers: [RedmineService.new])
|
|
expect(project).to receive(:services).and_return(services)
|
|
|
|
expect do
|
|
project.cache_has_external_issue_tracker
|
|
end.to change { project.has_external_issue_tracker}.to(true)
|
|
end
|
|
|
|
it 'stores false if there is no external_issue_tracker' do
|
|
services = double(:service, external_issue_trackers: [])
|
|
expect(project).to receive(:services).and_return(services)
|
|
|
|
expect do
|
|
project.cache_has_external_issue_tracker
|
|
end.to change { project.has_external_issue_tracker}.to(false)
|
|
end
|
|
|
|
it 'does not cache data when in a read-only GitLab instance' do
|
|
allow(Gitlab::Database).to receive(:read_only?) { true }
|
|
|
|
expect do
|
|
project.cache_has_external_issue_tracker
|
|
end.not_to change { project.has_external_issue_tracker }
|
|
end
|
|
end
|
|
|
|
describe '#cache_has_external_wiki' do
|
|
let(:project) { create(:project, has_external_wiki: nil) }
|
|
|
|
it 'stores true if there is any external_wikis' do
|
|
services = double(:service, external_wikis: [ExternalWikiService.new])
|
|
expect(project).to receive(:services).and_return(services)
|
|
|
|
expect do
|
|
project.cache_has_external_wiki
|
|
end.to change { project.has_external_wiki}.to(true)
|
|
end
|
|
|
|
it 'stores false if there is no external_wikis' do
|
|
services = double(:service, external_wikis: [])
|
|
expect(project).to receive(:services).and_return(services)
|
|
|
|
expect do
|
|
project.cache_has_external_wiki
|
|
end.to change { project.has_external_wiki}.to(false)
|
|
end
|
|
|
|
it 'does not cache data when in a read-only GitLab instance' do
|
|
allow(Gitlab::Database).to receive(:read_only?) { true }
|
|
|
|
expect do
|
|
project.cache_has_external_wiki
|
|
end.not_to change { project.has_external_wiki }
|
|
end
|
|
end
|
|
|
|
describe '#has_wiki?' do
|
|
let(:no_wiki_project) { create(:project, :wiki_disabled, has_external_wiki: false) }
|
|
let(:wiki_enabled_project) { create(:project) }
|
|
let(:external_wiki_project) { create(:project, has_external_wiki: true) }
|
|
|
|
it 'returns true if project is wiki enabled or has external wiki' do
|
|
expect(wiki_enabled_project).to have_wiki
|
|
expect(external_wiki_project).to have_wiki
|
|
expect(no_wiki_project).not_to have_wiki
|
|
end
|
|
end
|
|
|
|
describe '#external_wiki' do
|
|
let(:project) { create(:project) }
|
|
|
|
context 'with an active external wiki' do
|
|
before do
|
|
create(:service, project: project, type: 'ExternalWikiService', active: true)
|
|
project.external_wiki
|
|
end
|
|
|
|
it 'sets :has_external_wiki as true' do
|
|
expect(project.has_external_wiki).to be(true)
|
|
end
|
|
|
|
it 'sets :has_external_wiki as false if an external wiki service is destroyed later' do
|
|
expect(project.has_external_wiki).to be(true)
|
|
|
|
project.services.external_wikis.first.destroy
|
|
|
|
expect(project.has_external_wiki).to be(false)
|
|
end
|
|
end
|
|
|
|
context 'with an inactive external wiki' do
|
|
before do
|
|
create(:service, project: project, type: 'ExternalWikiService', active: false)
|
|
end
|
|
|
|
it 'sets :has_external_wiki as false' do
|
|
expect(project.has_external_wiki).to be(false)
|
|
end
|
|
end
|
|
|
|
context 'with no external wiki' do
|
|
before do
|
|
project.external_wiki
|
|
end
|
|
|
|
it 'sets :has_external_wiki as false' do
|
|
expect(project.has_external_wiki).to be(false)
|
|
end
|
|
|
|
it 'sets :has_external_wiki as true if an external wiki service is created later' do
|
|
expect(project.has_external_wiki).to be(false)
|
|
|
|
create(:service, project: project, type: 'ExternalWikiService', active: true)
|
|
|
|
expect(project.has_external_wiki).to be(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#star_count' do
|
|
it 'counts stars from multiple users' do
|
|
user1 = create(:user)
|
|
user2 = create(:user)
|
|
project = create(:project, :public)
|
|
|
|
expect(project.star_count).to eq(0)
|
|
|
|
user1.toggle_star(project)
|
|
expect(project.reload.star_count).to eq(1)
|
|
|
|
user2.toggle_star(project)
|
|
project.reload
|
|
expect(project.reload.star_count).to eq(2)
|
|
|
|
user1.toggle_star(project)
|
|
project.reload
|
|
expect(project.reload.star_count).to eq(1)
|
|
|
|
user2.toggle_star(project)
|
|
project.reload
|
|
expect(project.reload.star_count).to eq(0)
|
|
end
|
|
|
|
it 'counts stars on the right project' do
|
|
user = create(:user)
|
|
project1 = create(:project, :public)
|
|
project2 = create(:project, :public)
|
|
|
|
expect(project1.star_count).to eq(0)
|
|
expect(project2.star_count).to eq(0)
|
|
|
|
user.toggle_star(project1)
|
|
project1.reload
|
|
project2.reload
|
|
expect(project1.star_count).to eq(1)
|
|
expect(project2.star_count).to eq(0)
|
|
|
|
user.toggle_star(project1)
|
|
project1.reload
|
|
project2.reload
|
|
expect(project1.star_count).to eq(0)
|
|
expect(project2.star_count).to eq(0)
|
|
|
|
user.toggle_star(project2)
|
|
project1.reload
|
|
project2.reload
|
|
expect(project1.star_count).to eq(0)
|
|
expect(project2.star_count).to eq(1)
|
|
|
|
user.toggle_star(project2)
|
|
project1.reload
|
|
project2.reload
|
|
expect(project1.star_count).to eq(0)
|
|
expect(project2.star_count).to eq(0)
|
|
end
|
|
end
|
|
|
|
describe '#avatar_type' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'is true if avatar is image' do
|
|
project.update_attribute(:avatar, 'uploads/avatar.png')
|
|
expect(project.avatar_type).to be_truthy
|
|
end
|
|
|
|
it 'is false if avatar is html page' do
|
|
project.update_attribute(:avatar, 'uploads/avatar.html')
|
|
expect(project.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico'])
|
|
end
|
|
end
|
|
|
|
describe '#avatar_url' do
|
|
subject { project.avatar_url }
|
|
|
|
let(:project) { create(:project) }
|
|
|
|
context 'when avatar file is uploaded' do
|
|
let(:project) { create(:project, :public, :with_avatar) }
|
|
|
|
it 'shows correct url' do
|
|
expect(project.avatar_url).to eq(project.avatar.url)
|
|
expect(project.avatar_url(only_path: false)).to eq([Gitlab.config.gitlab.url, project.avatar.url].join)
|
|
end
|
|
end
|
|
|
|
context 'when avatar file in git' do
|
|
before do
|
|
allow(project).to receive(:avatar_in_git) { true }
|
|
end
|
|
|
|
let(:avatar_path) { "/#{project.full_path}/avatar" }
|
|
|
|
it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
|
|
end
|
|
|
|
context 'when git repo is empty' do
|
|
let(:project) { create(:project) }
|
|
|
|
it { is_expected.to eq nil }
|
|
end
|
|
end
|
|
|
|
describe '#pipeline_for' do
|
|
let(:project) { create(:project, :repository) }
|
|
let!(:pipeline) { create_pipeline }
|
|
|
|
shared_examples 'giving the correct pipeline' do
|
|
it { is_expected.to eq(pipeline) }
|
|
|
|
context 'return latest' do
|
|
let!(:pipeline2) { create_pipeline }
|
|
|
|
it { is_expected.to eq(pipeline2) }
|
|
end
|
|
end
|
|
|
|
context 'with explicit sha' do
|
|
subject { project.pipeline_for('master', pipeline.sha) }
|
|
|
|
it_behaves_like 'giving the correct pipeline'
|
|
end
|
|
|
|
context 'with implicit sha' do
|
|
subject { project.pipeline_for('master') }
|
|
|
|
it_behaves_like 'giving the correct pipeline'
|
|
end
|
|
|
|
def create_pipeline
|
|
create(:ci_pipeline,
|
|
project: project,
|
|
ref: 'master',
|
|
sha: project.commit('master').sha)
|
|
end
|
|
end
|
|
|
|
describe '#builds_enabled' do
|
|
let(:project) { create(:project) }
|
|
|
|
subject { project.builds_enabled }
|
|
|
|
it { expect(project.builds_enabled?).to be_truthy }
|
|
end
|
|
|
|
describe '.sort_by_attribute' do
|
|
it 'reorders the input relation by start count desc' do
|
|
project1 = create(:project, star_count: 2)
|
|
project2 = create(:project, star_count: 1)
|
|
project3 = create(:project)
|
|
|
|
projects = described_class.sort_by_attribute(:stars_desc)
|
|
|
|
expect(projects).to eq([project1, project2, project3])
|
|
end
|
|
end
|
|
|
|
describe '.with_shared_runners' do
|
|
subject { described_class.with_shared_runners }
|
|
|
|
context 'when shared runners are enabled for project' do
|
|
let!(:project) { create(:project, shared_runners_enabled: true) }
|
|
|
|
it "returns a project" do
|
|
is_expected.to eq([project])
|
|
end
|
|
end
|
|
|
|
context 'when shared runners are disabled for project' do
|
|
let!(:project) { create(:project, shared_runners_enabled: false) }
|
|
|
|
it "returns an empty array" do
|
|
is_expected.to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.cached_count', :use_clean_rails_memory_store_caching do
|
|
let(:group) { create(:group, :public) }
|
|
let!(:project1) { create(:project, :public, group: group) }
|
|
let!(:project2) { create(:project, :public, group: group) }
|
|
|
|
it 'returns total project count' do
|
|
expect(described_class).to receive(:count).once.and_call_original
|
|
|
|
3.times do
|
|
expect(described_class.cached_count).to eq(2)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.trending' do
|
|
let(:group) { create(:group, :public) }
|
|
let(:project1) { create(:project, :public, group: group) }
|
|
let(:project2) { create(:project, :public, group: group) }
|
|
|
|
before do
|
|
2.times do
|
|
create(:note_on_commit, project: project1)
|
|
end
|
|
|
|
create(:note_on_commit, project: project2)
|
|
|
|
TrendingProject.refresh!
|
|
end
|
|
|
|
subject { described_class.trending.to_a }
|
|
|
|
it 'sorts projects by the amount of notes in descending order' do
|
|
expect(subject).to eq([project1, project2])
|
|
end
|
|
|
|
it 'does not take system notes into account' do
|
|
10.times do
|
|
create(:note_on_commit, project: project2, system: true)
|
|
end
|
|
|
|
expect(described_class.trending.to_a).to eq([project1, project2])
|
|
end
|
|
end
|
|
|
|
describe '.starred_by' do
|
|
it 'returns only projects starred by the given user' do
|
|
user1 = create(:user)
|
|
user2 = create(:user)
|
|
project1 = create(:project)
|
|
project2 = create(:project)
|
|
create(:project)
|
|
user1.toggle_star(project1)
|
|
user2.toggle_star(project2)
|
|
|
|
expect(described_class.starred_by(user1)).to contain_exactly(project1)
|
|
end
|
|
end
|
|
|
|
describe '.visible_to_user' do
|
|
let!(:project) { create(:project, :private) }
|
|
let!(:user) { create(:user) }
|
|
|
|
subject { described_class.visible_to_user(user) }
|
|
|
|
describe 'when a user has access to a project' do
|
|
before do
|
|
project.add_user(user, Gitlab::Access::MAINTAINER)
|
|
end
|
|
|
|
it { is_expected.to eq([project]) }
|
|
end
|
|
|
|
describe 'when a user does not have access to any projects' do
|
|
it { is_expected.to eq([]) }
|
|
end
|
|
end
|
|
|
|
context 'repository storage by default' do
|
|
let(:project) { build(:project) }
|
|
|
|
before do
|
|
storages = {
|
|
'default' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories'),
|
|
'picked' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories')
|
|
}
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
|
end
|
|
|
|
it 'picks storage from ApplicationSetting' do
|
|
expect_any_instance_of(ApplicationSetting).to receive(:pick_repository_storage).and_return('picked')
|
|
|
|
expect(project.repository_storage).to eq('picked')
|
|
end
|
|
end
|
|
|
|
context 'shared runners by default' do
|
|
let(:project) { create(:project) }
|
|
|
|
subject { project.shared_runners_enabled }
|
|
|
|
context 'are enabled' do
|
|
before do
|
|
stub_application_setting(shared_runners_enabled: true)
|
|
end
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
context 'are disabled' do
|
|
before do
|
|
stub_application_setting(shared_runners_enabled: false)
|
|
end
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#any_runners?' do
|
|
context 'shared runners' do
|
|
let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) }
|
|
let(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
|
|
let(:shared_runner) { create(:ci_runner, :instance) }
|
|
|
|
context 'for shared runners disabled' do
|
|
let(:shared_runners_enabled) { false }
|
|
|
|
it 'has no runners available' do
|
|
expect(project.any_runners?).to be_falsey
|
|
end
|
|
|
|
it 'has a specific runner' do
|
|
specific_runner
|
|
|
|
expect(project.any_runners?).to be_truthy
|
|
end
|
|
|
|
it 'has a shared runner, but they are prohibited to use' do
|
|
shared_runner
|
|
|
|
expect(project.any_runners?).to be_falsey
|
|
end
|
|
|
|
it 'checks the presence of specific runner' do
|
|
specific_runner
|
|
|
|
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
|
|
end
|
|
|
|
it 'returns false if match cannot be found' do
|
|
specific_runner
|
|
|
|
expect(project.any_runners? { false }).to be_falsey
|
|
end
|
|
end
|
|
|
|
context 'for shared runners enabled' do
|
|
let(:shared_runners_enabled) { true }
|
|
|
|
it 'has a shared runner' do
|
|
shared_runner
|
|
|
|
expect(project.any_runners?).to be_truthy
|
|
end
|
|
|
|
it 'checks the presence of shared runner' do
|
|
shared_runner
|
|
|
|
expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
|
|
end
|
|
|
|
it 'returns false if match cannot be found' do
|
|
shared_runner
|
|
|
|
expect(project.any_runners? { false }).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'group runners' do
|
|
let(:project) { create(:project, group_runners_enabled: group_runners_enabled) }
|
|
let(:group) { create(:group, projects: [project]) }
|
|
let(:group_runner) { create(:ci_runner, :group, groups: [group]) }
|
|
|
|
context 'for group runners disabled' do
|
|
let(:group_runners_enabled) { false }
|
|
|
|
it 'has no runners available' do
|
|
expect(project.any_runners?).to be_falsey
|
|
end
|
|
|
|
it 'has a group runner, but they are prohibited to use' do
|
|
group_runner
|
|
|
|
expect(project.any_runners?).to be_falsey
|
|
end
|
|
end
|
|
|
|
context 'for group runners enabled' do
|
|
let(:group_runners_enabled) { true }
|
|
|
|
it 'has a group runner' do
|
|
group_runner
|
|
|
|
expect(project.any_runners?).to be_truthy
|
|
end
|
|
|
|
it 'checks the presence of group runner' do
|
|
group_runner
|
|
|
|
expect(project.any_runners? { |runner| runner == group_runner }).to be_truthy
|
|
end
|
|
|
|
it 'returns false if match cannot be found' do
|
|
group_runner
|
|
|
|
expect(project.any_runners? { false }).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#shared_runners' do
|
|
let!(:runner) { create(:ci_runner, :instance) }
|
|
|
|
subject { project.shared_runners }
|
|
|
|
context 'when shared runners are enabled for project' do
|
|
let!(:project) { create(:project, shared_runners_enabled: true) }
|
|
|
|
it "returns a list of shared runners" do
|
|
is_expected.to eq([runner])
|
|
end
|
|
end
|
|
|
|
context 'when shared runners are disabled for project' do
|
|
let!(:project) { create(:project, shared_runners_enabled: false) }
|
|
|
|
it "returns a empty list" do
|
|
is_expected.to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#visibility_level_allowed?' do
|
|
let(:project) { create(:project, :internal) }
|
|
|
|
context 'when checking on non-forked project' do
|
|
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
|
|
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
|
|
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_truthy }
|
|
end
|
|
|
|
context 'when checking on forked project' do
|
|
let(:project) { create(:project, :internal) }
|
|
let(:forked_project) { fork_project(project) }
|
|
|
|
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
|
|
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
|
|
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#pages_deployed?' do
|
|
let(:project) { create(:project) }
|
|
|
|
subject { project.pages_deployed? }
|
|
|
|
context 'if public folder does exist' do
|
|
before do
|
|
allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true)
|
|
end
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
context "if public folder doesn't exist" do
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#pages_url' do
|
|
let(:group) { create(:group, name: group_name) }
|
|
let(:project) { create(:project, namespace: group, name: project_name) }
|
|
let(:domain) { 'Example.com' }
|
|
|
|
subject { project.pages_url }
|
|
|
|
before do
|
|
allow(Settings.pages).to receive(:host).and_return(domain)
|
|
allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
|
|
end
|
|
|
|
context 'group page' do
|
|
let(:group_name) { 'Group' }
|
|
let(:project_name) { 'group.example.com' }
|
|
|
|
it { is_expected.to eq("http://group.example.com") }
|
|
end
|
|
|
|
context 'project page' do
|
|
let(:group_name) { 'Group' }
|
|
let(:project_name) { 'Project' }
|
|
|
|
it { is_expected.to eq("http://group.example.com/project") }
|
|
end
|
|
end
|
|
|
|
describe '#pages_group_url' do
|
|
let(:group) { create(:group, name: group_name) }
|
|
let(:project) { create(:project, namespace: group, name: project_name) }
|
|
let(:domain) { 'Example.com' }
|
|
let(:port) { 1234 }
|
|
|
|
subject { project.pages_group_url }
|
|
|
|
before do
|
|
allow(Settings.pages).to receive(:host).and_return(domain)
|
|
allow(Gitlab.config.pages).to receive(:url).and_return("http://example.com:#{port}")
|
|
end
|
|
|
|
context 'group page' do
|
|
let(:group_name) { 'Group' }
|
|
let(:project_name) { 'group.example.com' }
|
|
|
|
it { is_expected.to eq("http://group.example.com:#{port}") }
|
|
end
|
|
|
|
context 'project page' do
|
|
let(:group_name) { 'Group' }
|
|
let(:project_name) { 'Project' }
|
|
|
|
it { is_expected.to eq("http://group.example.com:#{port}") }
|
|
end
|
|
end
|
|
|
|
describe '.search' do
|
|
let(:project) { create(:project, description: 'kitten mittens') }
|
|
|
|
it 'returns projects with a matching name' do
|
|
expect(described_class.search(project.name)).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a partially matching name' do
|
|
expect(described_class.search(project.name[0..2])).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a matching name regardless of the casing' do
|
|
expect(described_class.search(project.name.upcase)).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a matching description' do
|
|
expect(described_class.search(project.description)).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a partially matching description' do
|
|
expect(described_class.search('kitten')).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a matching description regardless of the casing' do
|
|
expect(described_class.search('KITTEN')).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a matching path' do
|
|
expect(described_class.search(project.path)).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a partially matching path' do
|
|
expect(described_class.search(project.path[0..2])).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a matching path regardless of the casing' do
|
|
expect(described_class.search(project.path.upcase)).to eq([project])
|
|
end
|
|
|
|
describe 'with pending_delete project' do
|
|
let(:pending_delete_project) { create(:project, pending_delete: true) }
|
|
|
|
it 'shows pending deletion project' do
|
|
search_result = described_class.search(pending_delete_project.name)
|
|
|
|
expect(search_result).to eq([pending_delete_project])
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.optionally_search' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'searches for projects matching the query if one is given' do
|
|
relation = described_class.optionally_search(project.name)
|
|
|
|
expect(relation).to eq([project])
|
|
end
|
|
|
|
it 'returns the current relation if no search query is given' do
|
|
relation = described_class.where(id: project.id)
|
|
|
|
expect(relation.optionally_search).to eq(relation)
|
|
end
|
|
end
|
|
|
|
describe '.paginate_in_descending_order_using_id' do
|
|
let!(:project1) { create(:project) }
|
|
let!(:project2) { create(:project) }
|
|
|
|
it 'orders the relation in descending order' do
|
|
expect(described_class.paginate_in_descending_order_using_id)
|
|
.to eq([project2, project1])
|
|
end
|
|
|
|
it 'applies a limit to the relation' do
|
|
expect(described_class.paginate_in_descending_order_using_id(limit: 1))
|
|
.to eq([project2])
|
|
end
|
|
|
|
it 'limits projects by and ID when given' do
|
|
expect(described_class.paginate_in_descending_order_using_id(before: project2.id))
|
|
.to eq([project1])
|
|
end
|
|
end
|
|
|
|
describe '.including_namespace_and_owner' do
|
|
it 'eager loads the namespace and namespace owner' do
|
|
create(:project)
|
|
|
|
row = described_class.eager_load_namespace_and_owner.to_a.first
|
|
recorder = ActiveRecord::QueryRecorder.new { row.namespace.owner }
|
|
|
|
expect(recorder.count).to be_zero
|
|
end
|
|
end
|
|
|
|
describe '#expire_caches_before_rename' do
|
|
let(:project) { create(:project, :repository) }
|
|
let(:repo) { double(:repo, exists?: true) }
|
|
let(:wiki) { double(:wiki, exists?: true) }
|
|
|
|
it 'expires the caches of the repository and wiki' do
|
|
allow(Repository).to receive(:new)
|
|
.with('foo', project)
|
|
.and_return(repo)
|
|
|
|
allow(Repository).to receive(:new)
|
|
.with('foo.wiki', project)
|
|
.and_return(wiki)
|
|
|
|
expect(repo).to receive(:before_delete)
|
|
expect(wiki).to receive(:before_delete)
|
|
|
|
project.expire_caches_before_rename('foo')
|
|
end
|
|
end
|
|
|
|
describe '.search_by_title' do
|
|
let(:project) { create(:project, name: 'kittens') }
|
|
|
|
it 'returns projects with a matching name' do
|
|
expect(described_class.search_by_title(project.name)).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a partially matching name' do
|
|
expect(described_class.search_by_title('kitten')).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a matching name regardless of the casing' do
|
|
expect(described_class.search_by_title('KITTENS')).to eq([project])
|
|
end
|
|
end
|
|
|
|
context 'when checking projects from groups' do
|
|
let(:private_group) { create(:group, visibility_level: 0) }
|
|
let(:internal_group) { create(:group, visibility_level: 10) }
|
|
|
|
let(:private_project) { create(:project, :private, group: private_group) }
|
|
let(:internal_project) { create(:project, :internal, group: internal_group) }
|
|
|
|
context 'when group is private project can not be internal' do
|
|
it { expect(private_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_falsey }
|
|
end
|
|
|
|
context 'when group is internal project can not be public' do
|
|
it { expect(internal_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#create_repository' do
|
|
let(:project) { create(:project, :repository) }
|
|
let(:shell) { Gitlab::Shell.new }
|
|
|
|
before do
|
|
allow(project).to receive(:gitlab_shell).and_return(shell)
|
|
end
|
|
|
|
context 'using a regular repository' do
|
|
it 'creates the repository' do
|
|
expect(shell).to receive(:create_repository)
|
|
.with(project.repository_storage, project.disk_path)
|
|
.and_return(true)
|
|
|
|
expect(project.repository).to receive(:after_create)
|
|
|
|
expect(project.create_repository).to eq(true)
|
|
end
|
|
|
|
it 'adds an error if the repository could not be created' do
|
|
expect(shell).to receive(:create_repository)
|
|
.with(project.repository_storage, project.disk_path)
|
|
.and_return(false)
|
|
|
|
expect(project.repository).not_to receive(:after_create)
|
|
|
|
expect(project.create_repository).to eq(false)
|
|
expect(project.errors).not_to be_empty
|
|
end
|
|
end
|
|
|
|
context 'using a forked repository' do
|
|
it 'does nothing' do
|
|
expect(project).to receive(:forked?).and_return(true)
|
|
expect(shell).not_to receive(:create_repository)
|
|
|
|
project.create_repository
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#ensure_repository' do
|
|
let(:project) { create(:project, :repository) }
|
|
let(:shell) { Gitlab::Shell.new }
|
|
|
|
before do
|
|
allow(project).to receive(:gitlab_shell).and_return(shell)
|
|
end
|
|
|
|
it 'creates the repository if it not exist' do
|
|
allow(project).to receive(:repository_exists?)
|
|
.and_return(false)
|
|
|
|
allow(shell).to receive(:create_repository)
|
|
.with(project.repository_storage, project.disk_path)
|
|
.and_return(true)
|
|
|
|
expect(project).to receive(:create_repository).with(force: true)
|
|
|
|
project.ensure_repository
|
|
end
|
|
|
|
it 'does not create the repository if it exists' do
|
|
allow(project).to receive(:repository_exists?)
|
|
.and_return(true)
|
|
|
|
expect(project).not_to receive(:create_repository)
|
|
|
|
project.ensure_repository
|
|
end
|
|
|
|
it 'creates the repository if it is a fork' do
|
|
expect(project).to receive(:forked?).and_return(true)
|
|
|
|
allow(project).to receive(:repository_exists?)
|
|
.and_return(false)
|
|
|
|
expect(shell).to receive(:create_repository)
|
|
.with(project.repository_storage, project.disk_path)
|
|
.and_return(true)
|
|
|
|
project.ensure_repository
|
|
end
|
|
end
|
|
|
|
describe '#container_registry_url' do
|
|
let(:project) { create(:project) }
|
|
|
|
subject { project.container_registry_url }
|
|
|
|
before do
|
|
stub_container_registry_config(**registry_settings)
|
|
end
|
|
|
|
context 'for enabled registry' do
|
|
let(:registry_settings) do
|
|
{ enabled: true,
|
|
host_port: 'example.com' }
|
|
end
|
|
|
|
it { is_expected.not_to be_nil }
|
|
end
|
|
|
|
context 'for disabled registry' do
|
|
let(:registry_settings) do
|
|
{ enabled: false }
|
|
end
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
end
|
|
|
|
describe '#has_container_registry_tags?' do
|
|
let(:project) { create(:project) }
|
|
|
|
context 'when container registry is enabled' do
|
|
before do
|
|
stub_container_registry_config(enabled: true)
|
|
end
|
|
|
|
context 'when tags are present for multi-level registries' do
|
|
before do
|
|
create(:container_repository, project: project, name: 'image')
|
|
|
|
stub_container_registry_tags(repository: /image/,
|
|
tags: %w[latest rc1])
|
|
end
|
|
|
|
it 'should have image tags' do
|
|
expect(project).to have_container_registry_tags
|
|
end
|
|
end
|
|
|
|
context 'when tags are present for root repository' do
|
|
before do
|
|
stub_container_registry_tags(repository: project.full_path,
|
|
tags: %w[latest rc1 pre1])
|
|
end
|
|
|
|
it 'should have image tags' do
|
|
expect(project).to have_container_registry_tags
|
|
end
|
|
end
|
|
|
|
context 'when there are no tags at all' do
|
|
before do
|
|
stub_container_registry_tags(repository: :any, tags: [])
|
|
end
|
|
|
|
it 'should not have image tags' do
|
|
expect(project).not_to have_container_registry_tags
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when container registry is disabled' do
|
|
before do
|
|
stub_container_registry_config(enabled: false)
|
|
end
|
|
|
|
it 'should not have image tags' do
|
|
expect(project).not_to have_container_registry_tags
|
|
end
|
|
|
|
it 'should not check root repository tags' do
|
|
expect(project).not_to receive(:full_path)
|
|
expect(project).not_to have_container_registry_tags
|
|
end
|
|
|
|
it 'should iterate through container repositories' do
|
|
expect(project).to receive(:container_repositories)
|
|
expect(project).not_to have_container_registry_tags
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#ci_config_path=' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'sets nil' do
|
|
project.update!(ci_config_path: nil)
|
|
|
|
expect(project.ci_config_path).to be_nil
|
|
end
|
|
|
|
it 'sets a string' do
|
|
project.update!(ci_config_path: 'foo/.gitlab_ci.yml')
|
|
|
|
expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml')
|
|
end
|
|
|
|
it 'sets a string but removes all null characters' do
|
|
project.update!(ci_config_path: "f\0oo/\0/.gitlab_ci.yml")
|
|
|
|
expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml')
|
|
end
|
|
end
|
|
|
|
describe '#human_import_status_name' do
|
|
context 'when import_state exists' do
|
|
it 'returns the humanized status name' do
|
|
project = create(:project)
|
|
create(:import_state, :started, project: project)
|
|
|
|
expect(project.human_import_status_name).to eq("started")
|
|
end
|
|
end
|
|
|
|
context 'when import_state was not created yet' do
|
|
let(:project) { create(:project, :import_started) }
|
|
|
|
it 'ensures import_state is created and returns humanized status name' do
|
|
expect do
|
|
project.human_import_status_name
|
|
end.to change { ProjectImportState.count }.from(0).to(1)
|
|
end
|
|
|
|
it 'returns humanized status name' do
|
|
expect(project.human_import_status_name).to eq("started")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Project import job' do
|
|
let(:project) { create(:project, import_url: generate(:url)) }
|
|
|
|
before do
|
|
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository)
|
|
.with(project.repository_storage, project.disk_path, project.import_url)
|
|
.and_return(true)
|
|
|
|
# Works around https://github.com/rspec/rspec-mocks/issues/910
|
|
allow(described_class).to receive(:find).with(project.id).and_return(project)
|
|
expect(project.repository).to receive(:after_import)
|
|
.and_call_original
|
|
expect(project.wiki.repository).to receive(:after_import)
|
|
.and_call_original
|
|
end
|
|
|
|
it 'imports a project' do
|
|
expect_any_instance_of(RepositoryImportWorker).to receive(:perform).and_call_original
|
|
|
|
expect { project.import_schedule }.to change { project.import_jid }
|
|
expect(project.reload.import_status).to eq('finished')
|
|
end
|
|
end
|
|
|
|
describe 'project import state transitions' do
|
|
context 'state transition: [:started] => [:finished]' do
|
|
let(:after_import_service) { spy(:after_import_service) }
|
|
let(:housekeeping_service) { spy(:housekeeping_service) }
|
|
|
|
before do
|
|
allow(Projects::AfterImportService)
|
|
.to receive(:new) { after_import_service }
|
|
|
|
allow(after_import_service)
|
|
.to receive(:execute) { housekeeping_service.execute }
|
|
|
|
allow(Projects::HousekeepingService)
|
|
.to receive(:new) { housekeeping_service }
|
|
end
|
|
|
|
it 'resets project import_error' do
|
|
error_message = 'Some error'
|
|
mirror = create(:project_empty_repo, :import_started)
|
|
mirror.import_state.update(last_error: error_message)
|
|
|
|
expect { mirror.import_finish }.to change { mirror.import_error }.from(error_message).to(nil)
|
|
end
|
|
|
|
it 'performs housekeeping when an import of a fresh project is completed' do
|
|
project = create(:project_empty_repo, :import_started, import_type: :github)
|
|
|
|
project.import_finish
|
|
|
|
expect(after_import_service).to have_received(:execute)
|
|
expect(housekeeping_service).to have_received(:execute)
|
|
end
|
|
|
|
it 'does not perform housekeeping when project repository does not exist' do
|
|
project = create(:project, :import_started, import_type: :github)
|
|
|
|
project.import_finish
|
|
|
|
expect(housekeeping_service).not_to have_received(:execute)
|
|
end
|
|
|
|
it 'does not perform housekeeping when project does not have a valid import type' do
|
|
project = create(:project, :import_started, import_type: nil)
|
|
|
|
project.import_finish
|
|
|
|
expect(housekeeping_service).not_to have_received(:execute)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#latest_successful_builds_for' do
|
|
def create_pipeline(status = 'success')
|
|
create(:ci_pipeline, project: project,
|
|
sha: project.commit.sha,
|
|
ref: project.default_branch,
|
|
status: status)
|
|
end
|
|
|
|
def create_build(new_pipeline = pipeline, name = 'test')
|
|
create(:ci_build, :success, :artifacts,
|
|
pipeline: new_pipeline,
|
|
status: new_pipeline.status,
|
|
name: name)
|
|
end
|
|
|
|
let(:project) { create(:project, :repository) }
|
|
let(:pipeline) { create_pipeline }
|
|
|
|
context 'with many builds' do
|
|
it 'gives the latest builds from latest pipeline' do
|
|
pipeline1 = create_pipeline
|
|
pipeline2 = create_pipeline
|
|
build1_p2 = create_build(pipeline2, 'test')
|
|
create_build(pipeline1, 'test')
|
|
create_build(pipeline1, 'test2')
|
|
build2_p2 = create_build(pipeline2, 'test2')
|
|
|
|
latest_builds = project.latest_successful_builds_for
|
|
|
|
expect(latest_builds).to contain_exactly(build2_p2, build1_p2)
|
|
end
|
|
end
|
|
|
|
context 'with succeeded pipeline' do
|
|
let!(:build) { create_build }
|
|
|
|
context 'standalone pipeline' do
|
|
it 'returns builds for ref for default_branch' do
|
|
builds = project.latest_successful_builds_for
|
|
|
|
expect(builds).to contain_exactly(build)
|
|
end
|
|
|
|
it 'returns empty relation if the build cannot be found' do
|
|
builds = project.latest_successful_builds_for('TAIL')
|
|
|
|
expect(builds).to be_kind_of(ActiveRecord::Relation)
|
|
expect(builds).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'with some pending pipeline' do
|
|
before do
|
|
create_build(create_pipeline('pending'))
|
|
end
|
|
|
|
it 'gives the latest build from latest pipeline' do
|
|
latest_build = project.latest_successful_builds_for
|
|
|
|
expect(latest_build).to contain_exactly(build)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with pending pipeline' do
|
|
before do
|
|
pipeline.update(status: 'pending')
|
|
create_build(pipeline)
|
|
end
|
|
|
|
it 'returns empty relation' do
|
|
builds = project.latest_successful_builds_for
|
|
|
|
expect(builds).to be_kind_of(ActiveRecord::Relation)
|
|
expect(builds).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#add_import_job' do
|
|
let(:import_jid) { '123' }
|
|
|
|
context 'forked' do
|
|
let(:forked_from_project) { create(:project, :repository) }
|
|
let(:project) { create(:project) }
|
|
|
|
before do
|
|
fork_project(forked_from_project, nil, target_project: project)
|
|
end
|
|
|
|
it 'schedules a RepositoryForkWorker job' do
|
|
expect(RepositoryForkWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
|
|
|
|
expect(project.add_import_job).to eq(import_jid)
|
|
end
|
|
end
|
|
|
|
context 'not forked' do
|
|
it 'schedules a RepositoryImportWorker job' do
|
|
project = create(:project, import_url: generate(:url))
|
|
|
|
expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
|
|
expect(project.add_import_job).to eq(import_jid)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#gitlab_project_import?' do
|
|
subject(:project) { build(:project, import_type: 'gitlab_project') }
|
|
|
|
it { expect(project.gitlab_project_import?).to be true }
|
|
end
|
|
|
|
describe '#gitea_import?' do
|
|
subject(:project) { build(:project, import_type: 'gitea') }
|
|
|
|
it { expect(project.gitea_import?).to be true }
|
|
end
|
|
|
|
describe '#has_remote_mirror?' do
|
|
let(:project) { create(:project, :remote_mirror, :import_started) }
|
|
subject { project.has_remote_mirror? }
|
|
|
|
before do
|
|
allow_any_instance_of(RemoteMirror).to receive(:refresh_remote)
|
|
end
|
|
|
|
it 'returns true when a remote mirror is enabled' do
|
|
is_expected.to be_truthy
|
|
end
|
|
|
|
it 'returns false when remote mirror is disabled' do
|
|
project.remote_mirrors.first.update(enabled: false)
|
|
|
|
is_expected.to be_falsy
|
|
end
|
|
end
|
|
|
|
describe '#update_remote_mirrors' do
|
|
let(:project) { create(:project, :remote_mirror, :import_started) }
|
|
delegate :update_remote_mirrors, to: :project
|
|
|
|
before do
|
|
allow_any_instance_of(RemoteMirror).to receive(:refresh_remote)
|
|
end
|
|
|
|
it 'syncs enabled remote mirror' do
|
|
expect_any_instance_of(RemoteMirror).to receive(:sync)
|
|
|
|
update_remote_mirrors
|
|
end
|
|
|
|
it 'does nothing when remote mirror is disabled globally and not overridden' do
|
|
stub_application_setting(mirror_available: false)
|
|
project.remote_mirror_available_overridden = false
|
|
|
|
expect_any_instance_of(RemoteMirror).not_to receive(:sync)
|
|
|
|
update_remote_mirrors
|
|
end
|
|
|
|
it 'does not sync disabled remote mirrors' do
|
|
project.remote_mirrors.first.update(enabled: false)
|
|
|
|
expect_any_instance_of(RemoteMirror).not_to receive(:sync)
|
|
|
|
update_remote_mirrors
|
|
end
|
|
end
|
|
|
|
describe '#remote_mirror_available?' do
|
|
let(:project) { create(:project) }
|
|
|
|
context 'when remote mirror global setting is enabled' do
|
|
it 'returns true' do
|
|
expect(project.remote_mirror_available?).to be(true)
|
|
end
|
|
end
|
|
|
|
context 'when remote mirror global setting is disabled' do
|
|
before do
|
|
stub_application_setting(mirror_available: false)
|
|
end
|
|
|
|
it 'returns true when overridden' do
|
|
project.remote_mirror_available_overridden = true
|
|
|
|
expect(project.remote_mirror_available?).to be(true)
|
|
end
|
|
|
|
it 'returns false when not overridden' do
|
|
expect(project.remote_mirror_available?).to be(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#ancestors_upto', :nested_groups do
|
|
let(:parent) { create(:group) }
|
|
let(:child) { create(:group, parent: parent) }
|
|
let(:child2) { create(:group, parent: child) }
|
|
let(:project) { create(:project, namespace: child2) }
|
|
|
|
it 'returns all ancestors when no namespace is given' do
|
|
expect(project.ancestors_upto).to contain_exactly(child2, child, parent)
|
|
end
|
|
|
|
it 'includes ancestors upto but excluding the given ancestor' do
|
|
expect(project.ancestors_upto(parent)).to contain_exactly(child2, child)
|
|
end
|
|
end
|
|
|
|
describe '#lfs_enabled?' do
|
|
let(:project) { create(:project) }
|
|
|
|
shared_examples 'project overrides group' do
|
|
it 'returns true when enabled in project' do
|
|
project.update_attribute(:lfs_enabled, true)
|
|
|
|
expect(project.lfs_enabled?).to be_truthy
|
|
end
|
|
|
|
it 'returns false when disabled in project' do
|
|
project.update_attribute(:lfs_enabled, false)
|
|
|
|
expect(project.lfs_enabled?).to be_falsey
|
|
end
|
|
|
|
it 'returns the value from the namespace, when no value is set in project' do
|
|
expect(project.lfs_enabled?).to eq(project.namespace.lfs_enabled?)
|
|
end
|
|
end
|
|
|
|
context 'LFS disabled in group' do
|
|
before do
|
|
project.namespace.update_attribute(:lfs_enabled, false)
|
|
enable_lfs
|
|
end
|
|
|
|
it_behaves_like 'project overrides group'
|
|
end
|
|
|
|
context 'LFS enabled in group' do
|
|
before do
|
|
project.namespace.update_attribute(:lfs_enabled, true)
|
|
enable_lfs
|
|
end
|
|
|
|
it_behaves_like 'project overrides group'
|
|
end
|
|
|
|
describe 'LFS disabled globally' do
|
|
shared_examples 'it always returns false' do
|
|
it do
|
|
expect(project.lfs_enabled?).to be_falsey
|
|
expect(project.namespace.lfs_enabled?).to be_falsey
|
|
end
|
|
end
|
|
|
|
context 'when no values are set' do
|
|
it_behaves_like 'it always returns false'
|
|
end
|
|
|
|
context 'when all values are set to true' do
|
|
before do
|
|
project.namespace.update_attribute(:lfs_enabled, true)
|
|
project.update_attribute(:lfs_enabled, true)
|
|
end
|
|
|
|
it_behaves_like 'it always returns false'
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#change_head' do
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
it 'returns error if branch does not exist' do
|
|
expect(project.change_head('unexisted-branch')).to be false
|
|
expect(project.errors.size).to eq(1)
|
|
end
|
|
|
|
it 'calls the before_change_head and after_change_head methods' do
|
|
expect(project.repository).to receive(:before_change_head)
|
|
expect(project.repository).to receive(:after_change_head)
|
|
|
|
project.change_head(project.default_branch)
|
|
end
|
|
|
|
it 'creates the new reference with rugged' do
|
|
expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD', "refs/heads/#{project.default_branch}", shell: false)
|
|
|
|
project.change_head(project.default_branch)
|
|
end
|
|
|
|
it 'copies the gitattributes' do
|
|
expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch)
|
|
project.change_head(project.default_branch)
|
|
end
|
|
|
|
it 'reloads the default branch' do
|
|
expect(project).to receive(:reload_default_branch)
|
|
project.change_head(project.default_branch)
|
|
end
|
|
end
|
|
|
|
context 'forks' do
|
|
include ProjectForksHelper
|
|
|
|
let(:project) { create(:project, :public) }
|
|
let!(:forked_project) { fork_project(project) }
|
|
|
|
describe '#fork_network' do
|
|
it 'includes a fork of the project' do
|
|
expect(project.fork_network.projects).to include(forked_project)
|
|
end
|
|
|
|
it 'includes a fork of a fork' do
|
|
other_fork = fork_project(forked_project)
|
|
|
|
expect(project.fork_network.projects).to include(other_fork)
|
|
end
|
|
|
|
it 'includes sibling forks' do
|
|
other_fork = fork_project(project)
|
|
|
|
expect(forked_project.fork_network.projects).to include(other_fork)
|
|
end
|
|
|
|
it 'includes the base project' do
|
|
expect(forked_project.fork_network.projects).to include(project.reload)
|
|
end
|
|
end
|
|
|
|
describe '#in_fork_network_of?' do
|
|
it 'is true for a real fork' do
|
|
expect(forked_project.in_fork_network_of?(project)).to be_truthy
|
|
end
|
|
|
|
it 'is true for a fork of a fork', :postgresql do
|
|
other_fork = fork_project(forked_project)
|
|
|
|
expect(other_fork.in_fork_network_of?(project)).to be_truthy
|
|
end
|
|
|
|
it 'is true for sibling forks' do
|
|
sibling = fork_project(project)
|
|
|
|
expect(sibling.in_fork_network_of?(forked_project)).to be_truthy
|
|
end
|
|
|
|
it 'is false when another project is given' do
|
|
other_project = build_stubbed(:project)
|
|
|
|
expect(forked_project.in_fork_network_of?(other_project)).to be_falsy
|
|
end
|
|
end
|
|
|
|
describe '#fork_source' do
|
|
let!(:second_fork) { fork_project(forked_project) }
|
|
|
|
it 'returns the direct source if it exists' do
|
|
expect(second_fork.fork_source).to eq(forked_project)
|
|
end
|
|
|
|
it 'returns the root of the fork network when the directs source was deleted' do
|
|
forked_project.destroy
|
|
|
|
expect(second_fork.fork_source).to eq(project)
|
|
end
|
|
|
|
it 'returns nil if it is the root of the fork network' do
|
|
expect(project.fork_source).to be_nil
|
|
end
|
|
end
|
|
|
|
describe '#forks' do
|
|
it 'includes direct forks of the project' do
|
|
expect(project.forks).to contain_exactly(forked_project)
|
|
end
|
|
end
|
|
|
|
describe '#lfs_storage_project' do
|
|
it 'returns self for non-forks' do
|
|
expect(project.lfs_storage_project).to eq project
|
|
end
|
|
|
|
it 'returns the fork network root for forks' do
|
|
second_fork = fork_project(forked_project)
|
|
|
|
expect(second_fork.lfs_storage_project).to eq project
|
|
end
|
|
|
|
it 'returns self when fork_source is nil' do
|
|
expect(forked_project).to receive(:fork_source).and_return(nil)
|
|
|
|
expect(forked_project.lfs_storage_project).to eq forked_project
|
|
end
|
|
end
|
|
|
|
describe '#all_lfs_objects' do
|
|
let(:lfs_object) { create(:lfs_object) }
|
|
|
|
before do
|
|
project.lfs_objects << lfs_object
|
|
end
|
|
|
|
it 'returns the lfs object for a project' do
|
|
expect(project.all_lfs_objects).to contain_exactly(lfs_object)
|
|
end
|
|
|
|
it 'returns the lfs object for a fork' do
|
|
expect(forked_project.all_lfs_objects).to contain_exactly(lfs_object)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#pushes_since_gc' do
|
|
let(:project) { create(:project) }
|
|
|
|
after do
|
|
project.reset_pushes_since_gc
|
|
end
|
|
|
|
context 'without any pushes' do
|
|
it 'returns 0' do
|
|
expect(project.pushes_since_gc).to eq(0)
|
|
end
|
|
end
|
|
|
|
context 'with a number of pushes' do
|
|
it 'returns the number of pushes' do
|
|
3.times { project.increment_pushes_since_gc }
|
|
|
|
expect(project.pushes_since_gc).to eq(3)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#increment_pushes_since_gc' do
|
|
let(:project) { create(:project) }
|
|
|
|
after do
|
|
project.reset_pushes_since_gc
|
|
end
|
|
|
|
it 'increments the number of pushes since the last GC' do
|
|
3.times { project.increment_pushes_since_gc }
|
|
|
|
expect(project.pushes_since_gc).to eq(3)
|
|
end
|
|
end
|
|
|
|
describe '#reset_pushes_since_gc' do
|
|
let(:project) { create(:project) }
|
|
|
|
after do
|
|
project.reset_pushes_since_gc
|
|
end
|
|
|
|
it 'resets the number of pushes since the last GC' do
|
|
3.times { project.increment_pushes_since_gc }
|
|
|
|
project.reset_pushes_since_gc
|
|
|
|
expect(project.pushes_since_gc).to eq(0)
|
|
end
|
|
end
|
|
|
|
describe '#deployment_variables' do
|
|
context 'when project has no deployment service' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'returns an empty array' do
|
|
expect(project.deployment_variables).to eq []
|
|
end
|
|
end
|
|
|
|
context 'when project has a deployment service' do
|
|
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
|
|
it 'returns variables from this service' do
|
|
expect(project.deployment_variables).to include(
|
|
{ key: 'KUBE_TOKEN', value: project.deployment_platform.token, public: false }
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when user configured kubernetes from Integration > Kubernetes' do
|
|
let(:project) { create(:kubernetes_project) }
|
|
|
|
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
|
|
end
|
|
|
|
context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do
|
|
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
|
let(:project) { cluster.project }
|
|
|
|
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
|
|
end
|
|
|
|
context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do
|
|
let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace) }
|
|
let!(:cluster) { kubernetes_namespace.cluster }
|
|
let(:project) { kubernetes_namespace.project }
|
|
|
|
it 'should return token from kubernetes namespace' do
|
|
expect(project.deployment_variables).to include(
|
|
{ key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#default_environment' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'returns production environment when it exists' do
|
|
production = create(:environment, name: "production", project: project)
|
|
create(:environment, name: 'staging', project: project)
|
|
|
|
expect(project.default_environment).to eq(production)
|
|
end
|
|
|
|
it 'returns first environment when no production environment exists' do
|
|
create(:environment, name: 'staging', project: project)
|
|
create(:environment, name: 'foo', project: project)
|
|
|
|
expect(project.default_environment).to eq(project.environments.first)
|
|
end
|
|
|
|
it 'returns nil when no available environment exists' do
|
|
expect(project.default_environment).to be_nil
|
|
end
|
|
end
|
|
|
|
describe '#ci_variables_for' do
|
|
let(:project) { create(:project) }
|
|
|
|
let!(:ci_variable) do
|
|
create(:ci_variable, value: 'secret', project: project)
|
|
end
|
|
|
|
let!(:protected_variable) do
|
|
create(:ci_variable, :protected, value: 'protected', project: project)
|
|
end
|
|
|
|
subject { project.reload.ci_variables_for(ref: 'ref') }
|
|
|
|
before do
|
|
stub_application_setting(
|
|
default_branch_protection: Gitlab::Access::PROTECTION_NONE)
|
|
end
|
|
|
|
shared_examples 'ref is protected' do
|
|
it 'contains all the variables' do
|
|
is_expected.to contain_exactly(ci_variable, protected_variable)
|
|
end
|
|
end
|
|
|
|
context 'when the ref is not protected' do
|
|
it 'contains only the CI variables' do
|
|
is_expected.to contain_exactly(ci_variable)
|
|
end
|
|
end
|
|
|
|
context 'when the ref is a protected branch' do
|
|
before do
|
|
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
|
end
|
|
|
|
it_behaves_like 'ref is protected'
|
|
end
|
|
|
|
context 'when the ref is a protected tag' do
|
|
before do
|
|
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
|
end
|
|
|
|
it_behaves_like 'ref is protected'
|
|
end
|
|
end
|
|
|
|
describe '#any_lfs_file_locks?', :request_store do
|
|
set(:project) { create(:project) }
|
|
|
|
it 'returns false when there are no LFS file locks' do
|
|
expect(project.any_lfs_file_locks?).to be_falsey
|
|
end
|
|
|
|
it 'returns a cached true when there are LFS file locks' do
|
|
create(:lfs_file_lock, project: project)
|
|
|
|
expect(project.lfs_file_locks).to receive(:any?).once.and_call_original
|
|
|
|
2.times { expect(project.any_lfs_file_locks?).to be_truthy }
|
|
end
|
|
end
|
|
|
|
describe '#protected_for?' do
|
|
let(:project) { create(:project) }
|
|
|
|
subject { project.protected_for?('ref') }
|
|
|
|
context 'when the ref is not protected' do
|
|
before do
|
|
stub_application_setting(
|
|
default_branch_protection: Gitlab::Access::PROTECTION_NONE)
|
|
end
|
|
|
|
it 'returns false' do
|
|
is_expected.to be_falsey
|
|
end
|
|
end
|
|
|
|
context 'when the ref is a protected branch' do
|
|
before do
|
|
allow(project).to receive(:repository).and_call_original
|
|
allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(true)
|
|
create(:protected_branch, name: 'ref', project: project)
|
|
end
|
|
|
|
it 'returns true' do
|
|
is_expected.to be_truthy
|
|
end
|
|
end
|
|
|
|
context 'when the ref is a protected tag' do
|
|
before do
|
|
allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(false)
|
|
allow(project).to receive_message_chain(:repository, :tag_exists?).and_return(true)
|
|
create(:protected_tag, name: 'ref', project: project)
|
|
end
|
|
|
|
it 'returns true' do
|
|
is_expected.to be_truthy
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#update_project_statistics' do
|
|
let(:project) { create(:project) }
|
|
|
|
it "is called after creation" do
|
|
expect(project.statistics).to be_a ProjectStatistics
|
|
expect(project.statistics).to be_persisted
|
|
end
|
|
|
|
it "copies the namespace_id" do
|
|
expect(project.statistics.namespace_id).to eq project.namespace_id
|
|
end
|
|
|
|
it "updates the namespace_id when changed" do
|
|
namespace = create(:namespace)
|
|
project.update(namespace: namespace)
|
|
|
|
expect(project.statistics.namespace_id).to eq namespace.id
|
|
end
|
|
end
|
|
|
|
describe 'inside_path' do
|
|
let!(:project1) { create(:project, namespace: create(:namespace, path: 'name_pace')) }
|
|
let!(:project2) { create(:project) }
|
|
let!(:project3) { create(:project, namespace: create(:namespace, path: 'namespace')) }
|
|
let!(:path) { project1.namespace.full_path }
|
|
|
|
it 'returns correct project' do
|
|
expect(described_class.inside_path(path)).to eq([project1])
|
|
end
|
|
end
|
|
|
|
describe '#route_map_for' do
|
|
let(:project) { create(:project, :repository) }
|
|
let(:route_map) do
|
|
<<-MAP.strip_heredoc
|
|
- source: /source/(.*)/
|
|
public: '\\1'
|
|
MAP
|
|
end
|
|
|
|
before do
|
|
project.repository.create_file(User.last, '.gitlab/route-map.yml', route_map, message: 'Add .gitlab/route-map.yml', branch_name: 'master')
|
|
end
|
|
|
|
context 'when there is a .gitlab/route-map.yml at the commit' do
|
|
context 'when the route map is valid' do
|
|
it 'returns a route map' do
|
|
map = project.route_map_for(project.commit.sha)
|
|
expect(map).to be_a_kind_of(Gitlab::RouteMap)
|
|
end
|
|
end
|
|
|
|
context 'when the route map is invalid' do
|
|
let(:route_map) { 'INVALID' }
|
|
|
|
it 'returns nil' do
|
|
expect(project.route_map_for(project.commit.sha)).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when there is no .gitlab/route-map.yml at the commit' do
|
|
it 'returns nil' do
|
|
expect(project.route_map_for(project.commit.parent.sha)).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#public_path_for_source_path' do
|
|
let(:project) { create(:project, :repository) }
|
|
let(:route_map) do
|
|
Gitlab::RouteMap.new(<<-MAP.strip_heredoc)
|
|
- source: /source/(.*)/
|
|
public: '\\1'
|
|
MAP
|
|
end
|
|
let(:sha) { project.commit.id }
|
|
|
|
context 'when there is a route map' do
|
|
before do
|
|
allow(project).to receive(:route_map_for).with(sha).and_return(route_map)
|
|
end
|
|
|
|
context 'when the source path is mapped' do
|
|
it 'returns the public path' do
|
|
expect(project.public_path_for_source_path('source/file.html', sha)).to eq('file.html')
|
|
end
|
|
end
|
|
|
|
context 'when the source path is not mapped' do
|
|
it 'returns nil' do
|
|
expect(project.public_path_for_source_path('file.html', sha)).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when there is no route map' do
|
|
before do
|
|
allow(project).to receive(:route_map_for).with(sha).and_return(nil)
|
|
end
|
|
|
|
it 'returns nil' do
|
|
expect(project.public_path_for_source_path('source/file.html', sha)).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#parent' do
|
|
let(:project) { create(:project) }
|
|
|
|
it { expect(project.parent).to eq(project.namespace) }
|
|
end
|
|
|
|
describe '#parent_id' do
|
|
let(:project) { create(:project) }
|
|
|
|
it { expect(project.parent_id).to eq(project.namespace_id) }
|
|
end
|
|
|
|
describe '#parent_changed?' do
|
|
let(:project) { create(:project) }
|
|
|
|
before do
|
|
project.namespace_id = 7
|
|
end
|
|
|
|
it { expect(project.parent_changed?).to be_truthy }
|
|
end
|
|
|
|
def enable_lfs
|
|
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
|
|
end
|
|
|
|
describe '#pages_url' do
|
|
let(:group) { create(:group, name: 'Group') }
|
|
let(:nested_group) { create(:group, parent: group) }
|
|
let(:domain) { 'Example.com' }
|
|
|
|
subject { project.pages_url }
|
|
|
|
before do
|
|
allow(Settings.pages).to receive(:host).and_return(domain)
|
|
allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
|
|
end
|
|
|
|
context 'top-level group' do
|
|
let(:project) { create(:project, namespace: group, name: project_name) }
|
|
|
|
context 'group page' do
|
|
let(:project_name) { 'group.example.com' }
|
|
|
|
it { is_expected.to eq("http://group.example.com") }
|
|
end
|
|
|
|
context 'project page' do
|
|
let(:project_name) { 'Project' }
|
|
|
|
it { is_expected.to eq("http://group.example.com/project") }
|
|
end
|
|
end
|
|
|
|
context 'nested group' do
|
|
let(:project) { create(:project, namespace: nested_group, name: project_name) }
|
|
let(:expected_url) { "http://group.example.com/#{nested_group.path}/#{project.path}" }
|
|
|
|
context 'group page' do
|
|
let(:project_name) { 'group.example.com' }
|
|
|
|
it { is_expected.to eq(expected_url) }
|
|
end
|
|
|
|
context 'project page' do
|
|
let(:project_name) { 'Project' }
|
|
|
|
it { is_expected.to eq(expected_url) }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#http_url_to_repo' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'returns the url to the repo without a username' do
|
|
expect(project.http_url_to_repo).to eq("#{project.web_url}.git")
|
|
expect(project.http_url_to_repo).not_to include('@')
|
|
end
|
|
end
|
|
|
|
describe '#pipeline_status' do
|
|
let(:project) { create(:project, :repository) }
|
|
it 'builds a pipeline status' do
|
|
expect(project.pipeline_status).to be_a(Gitlab::Cache::Ci::ProjectPipelineStatus)
|
|
end
|
|
|
|
it 'hase a loaded pipeline status' do
|
|
expect(project.pipeline_status).to be_loaded
|
|
end
|
|
end
|
|
|
|
describe '#append_or_update_attribute' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'shows full error updating an invalid MR' do
|
|
error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\
|
|
' Validate fork Source project is not a fork of the target project'
|
|
|
|
expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) }
|
|
.to raise_error(ActiveRecord::RecordNotSaved, error_message)
|
|
end
|
|
|
|
it 'updates the project successfully' do
|
|
merge_request = create(:merge_request, target_project: project, source_project: project)
|
|
|
|
expect { project.append_or_update_attribute(:merge_requests, [merge_request]) }
|
|
.not_to raise_error
|
|
end
|
|
end
|
|
|
|
describe '#last_repository_updated_at' do
|
|
it 'sets to created_at upon creation' do
|
|
project = create(:project, created_at: 2.hours.ago)
|
|
|
|
expect(project.last_repository_updated_at.to_i).to eq(project.created_at.to_i)
|
|
end
|
|
end
|
|
|
|
describe '.public_or_visible_to_user' do
|
|
let!(:user) { create(:user) }
|
|
|
|
let!(:private_project) do
|
|
create(:project, :private, creator: user, namespace: user.namespace)
|
|
end
|
|
|
|
let!(:public_project) { create(:project, :public) }
|
|
|
|
context 'with a user' do
|
|
let(:projects) do
|
|
described_class.all.public_or_visible_to_user(user)
|
|
end
|
|
|
|
it 'includes projects the user has access to' do
|
|
expect(projects).to include(private_project)
|
|
end
|
|
|
|
it 'includes projects the user can see' do
|
|
expect(projects).to include(public_project)
|
|
end
|
|
end
|
|
|
|
context 'without a user' do
|
|
it 'only includes public projects' do
|
|
projects = described_class.all.public_or_visible_to_user
|
|
|
|
expect(projects).to eq([public_project])
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#pages_available?' do
|
|
let(:project) { create(:project, group: group) }
|
|
|
|
subject { project.pages_available? }
|
|
|
|
before do
|
|
allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
|
|
end
|
|
|
|
context 'when the project is in a top level namespace' do
|
|
let(:group) { create(:group) }
|
|
|
|
it { is_expected.to be(true) }
|
|
end
|
|
|
|
context 'when the project is in a subgroup' do
|
|
let(:group) { create(:group, :nested) }
|
|
|
|
it { is_expected.to be(false) }
|
|
end
|
|
end
|
|
|
|
describe '#remove_private_deploy_keys' do
|
|
let!(:project) { create(:project) }
|
|
|
|
context 'for a private deploy key' do
|
|
let!(:key) { create(:deploy_key, public: false) }
|
|
let!(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: key, project: project) }
|
|
|
|
context 'when the key is not linked to another project' do
|
|
it 'removes the key' do
|
|
project.remove_private_deploy_keys
|
|
|
|
expect(project.deploy_keys).not_to include(key)
|
|
end
|
|
end
|
|
|
|
context 'when the key is linked to another project' do
|
|
before do
|
|
another_project = create(:project)
|
|
create(:deploy_keys_project, deploy_key: key, project: another_project)
|
|
end
|
|
|
|
it 'does not remove the key' do
|
|
project.remove_private_deploy_keys
|
|
|
|
expect(project.deploy_keys).to include(key)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'for a public deploy key' do
|
|
let!(:key) { create(:deploy_key, public: true) }
|
|
let!(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: key, project: project) }
|
|
|
|
it 'does not remove the key' do
|
|
project.remove_private_deploy_keys
|
|
|
|
expect(project.deploy_keys).to include(key)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#remove_pages' do
|
|
let(:project) { create(:project) }
|
|
let(:namespace) { project.namespace }
|
|
let(:pages_path) { project.pages_path }
|
|
|
|
around do |example|
|
|
FileUtils.mkdir_p(pages_path)
|
|
begin
|
|
example.run
|
|
ensure
|
|
FileUtils.rm_rf(pages_path)
|
|
end
|
|
end
|
|
|
|
it 'removes the pages directory' do
|
|
expect_any_instance_of(Projects::UpdatePagesConfigurationService).to receive(:execute)
|
|
expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return(true)
|
|
expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, namespace.full_path, anything)
|
|
|
|
project.remove_pages
|
|
end
|
|
|
|
it 'is a no-op when there is no namespace' do
|
|
project.namespace.delete
|
|
project.reload
|
|
|
|
expect_any_instance_of(Projects::UpdatePagesConfigurationService).not_to receive(:execute)
|
|
expect_any_instance_of(Gitlab::PagesTransfer).not_to receive(:rename_project)
|
|
|
|
project.remove_pages
|
|
end
|
|
|
|
it 'is run when the project is destroyed' do
|
|
expect(project).to receive(:remove_pages).and_call_original
|
|
|
|
project.destroy
|
|
end
|
|
end
|
|
|
|
describe '#remove_export' do
|
|
let(:project) { create(:project, :with_export) }
|
|
|
|
it 'removes the export' do
|
|
project.remove_exports
|
|
|
|
expect(project.export_file_exists?).to be_falsey
|
|
end
|
|
end
|
|
|
|
describe '#forks_count' do
|
|
it 'returns the number of forks' do
|
|
project = build(:project)
|
|
|
|
expect_any_instance_of(Projects::ForksCountService).to receive(:count).and_return(1)
|
|
|
|
expect(project.forks_count).to eq(1)
|
|
end
|
|
end
|
|
|
|
context 'legacy storage' do
|
|
let(:project) { create(:project, :repository, :legacy_storage) }
|
|
let(:gitlab_shell) { Gitlab::Shell.new }
|
|
let(:project_storage) { project.send(:storage) }
|
|
|
|
before do
|
|
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
|
|
end
|
|
|
|
describe '#base_dir' do
|
|
it 'returns base_dir based on namespace only' do
|
|
expect(project.base_dir).to eq(project.namespace.full_path)
|
|
end
|
|
end
|
|
|
|
describe '#disk_path' do
|
|
it 'returns disk_path based on namespace and project path' do
|
|
expect(project.disk_path).to eq("#{project.namespace.full_path}/#{project.path}")
|
|
end
|
|
end
|
|
|
|
describe '#ensure_storage_path_exists' do
|
|
it 'delegates to gitlab_shell to ensure namespace is created' do
|
|
expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage, project.base_dir)
|
|
|
|
project.ensure_storage_path_exists
|
|
end
|
|
end
|
|
|
|
describe '#legacy_storage?' do
|
|
it 'returns true when storage_version is nil' do
|
|
project = build(:project, storage_version: nil)
|
|
|
|
expect(project.legacy_storage?).to be_truthy
|
|
end
|
|
|
|
it 'returns true when the storage_version is 0' do
|
|
project = build(:project, storage_version: 0)
|
|
|
|
expect(project.legacy_storage?).to be_truthy
|
|
end
|
|
end
|
|
|
|
describe '#hashed_storage?' do
|
|
it 'returns false' do
|
|
expect(project.hashed_storage?(:repository)).to be_falsey
|
|
end
|
|
end
|
|
|
|
describe '#pages_path' do
|
|
it 'returns a path where pages are stored' do
|
|
expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
|
|
end
|
|
end
|
|
|
|
describe '#migrate_to_hashed_storage!' do
|
|
it 'returns true' do
|
|
expect(project.migrate_to_hashed_storage!).to be_truthy
|
|
end
|
|
|
|
it 'flags as read-only' do
|
|
expect { project.migrate_to_hashed_storage! }.to change { project.repository_read_only }.to(true)
|
|
end
|
|
|
|
it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the project repo is in use' do
|
|
Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase
|
|
|
|
expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in)
|
|
|
|
project.migrate_to_hashed_storage!
|
|
end
|
|
|
|
it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the wiki repo is in use' do
|
|
Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase
|
|
|
|
expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in)
|
|
|
|
project.migrate_to_hashed_storage!
|
|
end
|
|
|
|
it 'schedules ProjectMigrateHashedStorageWorker' do
|
|
expect(ProjectMigrateHashedStorageWorker).to receive(:perform_async).with(project.id)
|
|
|
|
project.migrate_to_hashed_storage!
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'hashed storage' do
|
|
let(:project) { create(:project, :repository, skip_disk_validation: true) }
|
|
let(:gitlab_shell) { Gitlab::Shell.new }
|
|
let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) }
|
|
let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) }
|
|
let(:hashed_path) { File.join(hashed_prefix, hash) }
|
|
|
|
before do
|
|
stub_application_setting(hashed_storage_enabled: true)
|
|
end
|
|
|
|
describe '#legacy_storage?' do
|
|
it 'returns false' do
|
|
expect(project.legacy_storage?).to be_falsey
|
|
end
|
|
end
|
|
|
|
describe '#hashed_storage?' do
|
|
it 'returns true if rolled out' do
|
|
expect(project.hashed_storage?(:attachments)).to be_truthy
|
|
end
|
|
|
|
it 'returns false when not rolled out yet' do
|
|
project.storage_version = 1
|
|
|
|
expect(project.hashed_storage?(:attachments)).to be_falsey
|
|
end
|
|
end
|
|
|
|
describe '#base_dir' do
|
|
it 'returns base_dir based on hash of project id' do
|
|
expect(project.base_dir).to eq(hashed_prefix)
|
|
end
|
|
end
|
|
|
|
describe '#disk_path' do
|
|
it 'returns disk_path based on hash of project id' do
|
|
expect(project.disk_path).to eq(hashed_path)
|
|
end
|
|
end
|
|
|
|
describe '#ensure_storage_path_exists' do
|
|
it 'delegates to gitlab_shell to ensure namespace is created' do
|
|
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
|
|
|
|
expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage, hashed_prefix)
|
|
|
|
project.ensure_storage_path_exists
|
|
end
|
|
end
|
|
|
|
describe '#pages_path' do
|
|
it 'returns a path where pages are stored' do
|
|
expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
|
|
end
|
|
end
|
|
|
|
describe '#migrate_to_hashed_storage!' do
|
|
it 'returns nil' do
|
|
expect(project.migrate_to_hashed_storage!).to be_nil
|
|
end
|
|
|
|
it 'does not flag as read-only' do
|
|
expect { project.migrate_to_hashed_storage! }.not_to change { project.repository_read_only }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#gl_repository' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'delegates to Gitlab::GlRepository.gl_repository' do
|
|
expect(Gitlab::GlRepository).to receive(:gl_repository).with(project, true)
|
|
|
|
project.gl_repository(is_wiki: true)
|
|
end
|
|
end
|
|
|
|
describe '#has_ci?' do
|
|
set(:project) { create(:project) }
|
|
let(:repository) { double }
|
|
|
|
before do
|
|
expect(project).to receive(:repository) { repository }
|
|
end
|
|
|
|
context 'when has .gitlab-ci.yml' do
|
|
before do
|
|
expect(repository).to receive(:gitlab_ci_yml) { 'content' }
|
|
end
|
|
|
|
it "CI is available" do
|
|
expect(project).to have_ci
|
|
end
|
|
end
|
|
|
|
context 'when there is no .gitlab-ci.yml' do
|
|
before do
|
|
expect(repository).to receive(:gitlab_ci_yml) { nil }
|
|
end
|
|
|
|
it "CI is available" do
|
|
expect(project).to have_ci
|
|
end
|
|
|
|
context 'when auto devops is disabled' do
|
|
before do
|
|
stub_application_setting(auto_devops_enabled: false)
|
|
end
|
|
|
|
it "CI is not available" do
|
|
expect(project).not_to have_ci
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#auto_devops_enabled?' do
|
|
before do
|
|
allow(Feature).to receive(:enabled?).and_call_original
|
|
Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0)
|
|
end
|
|
|
|
set(:project) { create(:project) }
|
|
|
|
subject { project.auto_devops_enabled? }
|
|
|
|
context 'when enabled in settings' do
|
|
before do
|
|
stub_application_setting(auto_devops_enabled: true)
|
|
end
|
|
|
|
it { is_expected.to be_truthy }
|
|
|
|
context 'when explicitly enabled' do
|
|
before do
|
|
create(:project_auto_devops, project: project)
|
|
end
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
context 'when explicitly disabled' do
|
|
before do
|
|
create(:project_auto_devops, project: project, enabled: false)
|
|
end
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
end
|
|
|
|
context 'when disabled in settings' do
|
|
before do
|
|
stub_application_setting(auto_devops_enabled: false)
|
|
end
|
|
|
|
it { is_expected.to be_falsey }
|
|
|
|
context 'when explicitly enabled' do
|
|
before do
|
|
create(:project_auto_devops, project: project)
|
|
end
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
context 'when force_autodevops_on_by_default is enabled for the project' do
|
|
before do
|
|
Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100)
|
|
end
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#has_auto_devops_implicitly_enabled?' do
|
|
set(:project) { create(:project) }
|
|
|
|
context 'when disabled in settings' do
|
|
before do
|
|
stub_application_setting(auto_devops_enabled: false)
|
|
end
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
expect(project).not_to have_auto_devops_implicitly_enabled
|
|
end
|
|
end
|
|
|
|
context 'when enabled in settings' do
|
|
before do
|
|
stub_application_setting(auto_devops_enabled: true)
|
|
end
|
|
|
|
it 'auto devops is implicitly disabled' do
|
|
expect(project).to have_auto_devops_implicitly_enabled
|
|
end
|
|
|
|
context 'when explicitly disabled' do
|
|
before do
|
|
create(:project_auto_devops, project: project, enabled: false)
|
|
end
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
expect(project).not_to have_auto_devops_implicitly_enabled
|
|
end
|
|
end
|
|
|
|
context 'when explicitly enabled' do
|
|
before do
|
|
create(:project_auto_devops, project: project, enabled: true)
|
|
end
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
expect(project).not_to have_auto_devops_implicitly_enabled
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#has_auto_devops_implicitly_disabled?' do
|
|
before do
|
|
allow(Feature).to receive(:enabled?).and_call_original
|
|
Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0)
|
|
end
|
|
|
|
set(:project) { create(:project) }
|
|
|
|
context 'when enabled in settings' do
|
|
before do
|
|
stub_application_setting(auto_devops_enabled: true)
|
|
end
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
expect(project).not_to have_auto_devops_implicitly_disabled
|
|
end
|
|
end
|
|
|
|
context 'when disabled in settings' do
|
|
before do
|
|
stub_application_setting(auto_devops_enabled: false)
|
|
end
|
|
|
|
it 'auto devops is implicitly disabled' do
|
|
expect(project).to have_auto_devops_implicitly_disabled
|
|
end
|
|
|
|
context 'when force_autodevops_on_by_default is enabled for the project' do
|
|
before do
|
|
Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100)
|
|
end
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
expect(project).not_to have_auto_devops_implicitly_disabled
|
|
end
|
|
end
|
|
|
|
context 'when explicitly disabled' do
|
|
before do
|
|
create(:project_auto_devops, project: project, enabled: false)
|
|
end
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
expect(project).not_to have_auto_devops_implicitly_disabled
|
|
end
|
|
end
|
|
|
|
context 'when explicitly enabled' do
|
|
before do
|
|
create(:project_auto_devops, project: project, enabled: true)
|
|
end
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
expect(project).not_to have_auto_devops_implicitly_disabled
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context '#auto_devops_variables' do
|
|
set(:project) { create(:project) }
|
|
|
|
subject { project.auto_devops_variables }
|
|
|
|
context 'when enabled in instance settings' do
|
|
before do
|
|
stub_application_setting(auto_devops_enabled: true)
|
|
end
|
|
|
|
context 'when domain is empty' do
|
|
before do
|
|
stub_application_setting(auto_devops_domain: nil)
|
|
end
|
|
|
|
it 'variables does not include AUTO_DEVOPS_DOMAIN' do
|
|
is_expected.not_to include(domain_variable)
|
|
end
|
|
end
|
|
|
|
context 'when domain is configured' do
|
|
before do
|
|
stub_application_setting(auto_devops_domain: 'example.com')
|
|
end
|
|
|
|
it 'variables includes AUTO_DEVOPS_DOMAIN' do
|
|
is_expected.to include(domain_variable)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when explicitly enabled' do
|
|
context 'when domain is empty' do
|
|
before do
|
|
create(:project_auto_devops, project: project, domain: nil)
|
|
end
|
|
|
|
it 'variables does not include AUTO_DEVOPS_DOMAIN' do
|
|
is_expected.not_to include(domain_variable)
|
|
end
|
|
end
|
|
|
|
context 'when domain is configured' do
|
|
before do
|
|
create(:project_auto_devops, project: project, domain: 'example.com')
|
|
end
|
|
|
|
it 'variables includes AUTO_DEVOPS_DOMAIN' do
|
|
is_expected.to include(domain_variable)
|
|
end
|
|
end
|
|
end
|
|
|
|
def domain_variable
|
|
{ key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }
|
|
end
|
|
end
|
|
|
|
describe '#latest_successful_builds_for' do
|
|
let(:project) { build(:project) }
|
|
|
|
before do
|
|
allow(project).to receive(:default_branch).and_return('master')
|
|
end
|
|
|
|
context 'without a ref' do
|
|
it 'returns a pipeline for the default branch' do
|
|
expect(project)
|
|
.to receive(:latest_successful_pipeline_for_default_branch)
|
|
|
|
project.latest_successful_pipeline_for
|
|
end
|
|
end
|
|
|
|
context 'with the ref set to the default branch' do
|
|
it 'returns a pipeline for the default branch' do
|
|
expect(project)
|
|
.to receive(:latest_successful_pipeline_for_default_branch)
|
|
|
|
project.latest_successful_pipeline_for(project.default_branch)
|
|
end
|
|
end
|
|
|
|
context 'with a ref that is not the default branch' do
|
|
it 'returns the latest successful pipeline for the given ref' do
|
|
expect(project.pipelines).to receive(:latest_successful_for).with('foo')
|
|
|
|
project.latest_successful_pipeline_for('foo')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#check_repository_path_availability' do
|
|
let(:project) { build(:project) }
|
|
|
|
it 'skips gitlab-shell exists?' do
|
|
project.skip_disk_validation = true
|
|
|
|
expect(project.gitlab_shell).not_to receive(:exists?)
|
|
expect(project.check_repository_path_availability).to be_truthy
|
|
end
|
|
end
|
|
|
|
describe '#latest_successful_pipeline_for_default_branch' do
|
|
let(:project) { build(:project) }
|
|
|
|
before do
|
|
allow(project).to receive(:default_branch).and_return('master')
|
|
end
|
|
|
|
it 'memoizes and returns the latest successful pipeline for the default branch' do
|
|
pipeline = double(:pipeline)
|
|
|
|
expect(project.pipelines).to receive(:latest_successful_for)
|
|
.with(project.default_branch)
|
|
.and_return(pipeline)
|
|
.once
|
|
|
|
2.times do
|
|
expect(project.latest_successful_pipeline_for_default_branch)
|
|
.to eq(pipeline)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#after_import' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'runs the correct hooks' do
|
|
expect(project.repository).to receive(:after_import)
|
|
expect(project.wiki.repository).to receive(:after_import)
|
|
expect(project).to receive(:import_finish)
|
|
expect(project).to receive(:update_project_counter_caches)
|
|
expect(project).to receive(:remove_import_jid)
|
|
expect(project).to receive(:after_create_default_branch)
|
|
expect(project).to receive(:refresh_markdown_cache!)
|
|
|
|
project.after_import
|
|
end
|
|
|
|
context 'branch protection' do
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
it 'does not protect when branch protection is disabled' do
|
|
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
|
|
|
|
project.after_import
|
|
|
|
expect(project.protected_branches).to be_empty
|
|
end
|
|
|
|
it "gives developer access to push when branch protection is set to 'developers can push'" do
|
|
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
|
|
|
|
project.after_import
|
|
|
|
expect(project.protected_branches).not_to be_empty
|
|
expect(project.default_branch).to eq(project.protected_branches.first.name)
|
|
expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
|
|
end
|
|
|
|
it "gives developer access to merge when branch protection is set to 'developers can merge'" do
|
|
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
|
|
|
|
project.after_import
|
|
|
|
expect(project.protected_branches).not_to be_empty
|
|
expect(project.default_branch).to eq(project.protected_branches.first.name)
|
|
expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
|
|
end
|
|
|
|
it 'protects default branch' do
|
|
project.after_import
|
|
|
|
expect(project.protected_branches).not_to be_empty
|
|
expect(project.default_branch).to eq(project.protected_branches.first.name)
|
|
expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
|
|
expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#update_project_counter_caches' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'updates all project counter caches' do
|
|
expect_any_instance_of(Projects::OpenIssuesCountService)
|
|
.to receive(:refresh_cache)
|
|
.and_call_original
|
|
|
|
expect_any_instance_of(Projects::OpenMergeRequestsCountService)
|
|
.to receive(:refresh_cache)
|
|
.and_call_original
|
|
|
|
project.update_project_counter_caches
|
|
end
|
|
end
|
|
|
|
describe '#remove_import_jid', :clean_gitlab_redis_cache do
|
|
let(:project) { }
|
|
|
|
context 'without an import JID' do
|
|
it 'does nothing' do
|
|
project = create(:project)
|
|
|
|
expect(Gitlab::SidekiqStatus)
|
|
.not_to receive(:unset)
|
|
|
|
project.remove_import_jid
|
|
end
|
|
end
|
|
|
|
context 'with an import JID' do
|
|
it 'unsets the import JID' do
|
|
project = create(:project)
|
|
create(:import_state, project: project, jid: '123')
|
|
|
|
expect(Gitlab::SidekiqStatus)
|
|
.to receive(:unset)
|
|
.with('123')
|
|
.and_call_original
|
|
|
|
project.remove_import_jid
|
|
|
|
expect(project.import_jid).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#wiki_repository_exists?' do
|
|
it 'returns true when the wiki repository exists' do
|
|
project = create(:project, :wiki_repo)
|
|
|
|
expect(project.wiki_repository_exists?).to eq(true)
|
|
end
|
|
|
|
it 'returns false when the wiki repository does not exist' do
|
|
project = create(:project)
|
|
|
|
expect(project.wiki_repository_exists?).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe '#write_repository_config' do
|
|
set(:project) { create(:project, :repository) }
|
|
|
|
it 'writes full path in .git/config when key is missing' do
|
|
project.write_repository_config
|
|
|
|
expect(rugged_config['gitlab.fullpath']).to eq project.full_path
|
|
end
|
|
|
|
it 'updates full path in .git/config when key is present' do
|
|
project.write_repository_config(gl_full_path: 'old/path')
|
|
|
|
expect { project.write_repository_config }.to change { rugged_config['gitlab.fullpath'] }.from('old/path').to(project.full_path)
|
|
end
|
|
|
|
it 'does not raise an error with an empty repository' do
|
|
project = create(:project_empty_repo)
|
|
|
|
expect { project.write_repository_config }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
describe '#execute_hooks' do
|
|
let(:data) { { ref: 'refs/heads/master', data: 'data' } }
|
|
it 'executes active projects hooks with the specified scope' do
|
|
hook = create(:project_hook, merge_requests_events: false, push_events: true)
|
|
expect(ProjectHook).to receive(:select_active)
|
|
.with(:push_hooks, data)
|
|
.and_return([hook])
|
|
project = create(:project, hooks: [hook])
|
|
|
|
expect_any_instance_of(ProjectHook).to receive(:async_execute).once
|
|
|
|
project.execute_hooks(data, :push_hooks)
|
|
end
|
|
|
|
it 'does not execute project hooks that dont match the specified scope' do
|
|
hook = create(:project_hook, merge_requests_events: true, push_events: false)
|
|
project = create(:project, hooks: [hook])
|
|
|
|
expect_any_instance_of(ProjectHook).not_to receive(:async_execute).once
|
|
|
|
project.execute_hooks(data, :push_hooks)
|
|
end
|
|
|
|
it 'does not execute project hooks which are not active' do
|
|
hook = create(:project_hook, push_events: true)
|
|
expect(ProjectHook).to receive(:select_active)
|
|
.with(:push_hooks, data)
|
|
.and_return([])
|
|
project = create(:project, hooks: [hook])
|
|
|
|
expect_any_instance_of(ProjectHook).not_to receive(:async_execute).once
|
|
|
|
project.execute_hooks(data, :push_hooks)
|
|
end
|
|
|
|
it 'executes the system hooks with the specified scope' do
|
|
expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(data, :merge_request_hooks)
|
|
|
|
project = build(:project)
|
|
project.execute_hooks(data, :merge_request_hooks)
|
|
end
|
|
|
|
it 'executes the system hooks when inside a transaction' do
|
|
allow_any_instance_of(WebHookService).to receive(:execute)
|
|
|
|
create(:system_hook, merge_requests_events: true)
|
|
|
|
project = build(:project)
|
|
|
|
# Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
|
|
# but since the entire spec run takes place in a transaction, we never
|
|
# actually get to the `after_commit` hook that queues these jobs.
|
|
expect do
|
|
project.transaction do
|
|
project.execute_hooks(data, :merge_request_hooks)
|
|
end
|
|
end.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
|
|
end
|
|
end
|
|
|
|
describe '#badges' do
|
|
let(:project_group) { create(:group) }
|
|
let(:project) { create(:project, path: 'avatar', namespace: project_group) }
|
|
|
|
before do
|
|
create_list(:project_badge, 2, project: project)
|
|
create(:group_badge, group: project_group)
|
|
end
|
|
|
|
it 'returns the project and the project group badges' do
|
|
create(:group_badge, group: create(:group))
|
|
|
|
expect(Badge.count).to eq 4
|
|
expect(project.badges.count).to eq 3
|
|
end
|
|
|
|
if Group.supports_nested_groups?
|
|
context 'with nested_groups' do
|
|
let(:parent_group) { create(:group) }
|
|
|
|
before do
|
|
create_list(:group_badge, 2, group: project_group)
|
|
project_group.update(parent: parent_group)
|
|
end
|
|
|
|
it 'returns the project and the project nested groups badges' do
|
|
expect(project.badges.count).to eq 5
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with cross project merge requests' do
|
|
let(:user) { create(:user) }
|
|
let(:target_project) { create(:project, :repository) }
|
|
let(:project) { fork_project(target_project, nil, repository: true) }
|
|
let!(:merge_request) do
|
|
create(
|
|
:merge_request,
|
|
target_project: target_project,
|
|
target_branch: 'target-branch',
|
|
source_project: project,
|
|
source_branch: 'awesome-feature-1',
|
|
allow_collaboration: true
|
|
)
|
|
end
|
|
|
|
before do
|
|
target_project.add_developer(user)
|
|
end
|
|
|
|
describe '#merge_requests_allowing_push_to_user' do
|
|
it 'returns open merge requests for which the user has developer access to the target project' do
|
|
expect(project.merge_requests_allowing_push_to_user(user)).to include(merge_request)
|
|
end
|
|
|
|
it 'does not include closed merge requests' do
|
|
merge_request.close
|
|
|
|
expect(project.merge_requests_allowing_push_to_user(user)).to be_empty
|
|
end
|
|
|
|
it 'does not include merge requests for guest users' do
|
|
guest = create(:user)
|
|
target_project.add_guest(guest)
|
|
|
|
expect(project.merge_requests_allowing_push_to_user(guest)).to be_empty
|
|
end
|
|
|
|
it 'does not include the merge request for other users' do
|
|
other_user = create(:user)
|
|
|
|
expect(project.merge_requests_allowing_push_to_user(other_user)).to be_empty
|
|
end
|
|
|
|
it 'is empty when no user is passed' do
|
|
expect(project.merge_requests_allowing_push_to_user(nil)).to be_empty
|
|
end
|
|
end
|
|
|
|
describe '#branch_allows_collaboration_push?' do
|
|
it 'allows access if the user can merge the merge request' do
|
|
expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
|
|
.to be_truthy
|
|
end
|
|
|
|
it 'allows access when there are merge requests open but no branch name is given' do
|
|
expect(project.branch_allows_collaboration?(user, nil))
|
|
.to be_truthy
|
|
end
|
|
|
|
it 'does not allow guest users access' do
|
|
guest = create(:user)
|
|
target_project.add_guest(guest)
|
|
|
|
expect(project.branch_allows_collaboration?(guest, 'awesome-feature-1'))
|
|
.to be_falsy
|
|
end
|
|
|
|
it 'does not allow access to branches for which the merge request was closed' do
|
|
create(:merge_request, :closed,
|
|
target_project: target_project,
|
|
target_branch: 'target-branch',
|
|
source_project: project,
|
|
source_branch: 'rejected-feature-1',
|
|
allow_collaboration: true)
|
|
|
|
expect(project.branch_allows_collaboration?(user, 'rejected-feature-1'))
|
|
.to be_falsy
|
|
end
|
|
|
|
it 'does not allow access if the user cannot merge the merge request' do
|
|
create(:protected_branch, :maintainers_can_push, project: target_project, name: 'target-branch')
|
|
|
|
expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
|
|
.to be_falsy
|
|
end
|
|
|
|
it 'caches the result' do
|
|
control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
|
|
|
|
expect { 3.times { project.branch_allows_collaboration?(user, 'awesome-feature-1') } }
|
|
.not_to exceed_query_limit(control)
|
|
end
|
|
|
|
context 'when the requeststore is active', :request_store do
|
|
it 'only queries per project across instances' do
|
|
control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
|
|
|
|
expect { 2.times { described_class.find(project.id).branch_allows_collaboration?(user, 'awesome-feature-1') } }
|
|
.not_to exceed_query_limit(control).with_threshold(2)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#pages_https_only?" do
|
|
subject { build(:project) }
|
|
|
|
context "when HTTPS pages are disabled" do
|
|
it { is_expected.not_to be_pages_https_only }
|
|
end
|
|
|
|
context "when HTTPS pages are enabled", :https_pages_enabled do
|
|
it { is_expected.to be_pages_https_only }
|
|
end
|
|
end
|
|
|
|
describe "#pages_https_only? validation", :https_pages_enabled do
|
|
subject(:project) do
|
|
# set-up dirty object:
|
|
create(:project, pages_https_only: false).tap do |p|
|
|
p.pages_https_only = true
|
|
end
|
|
end
|
|
|
|
context "when no domains are associated" do
|
|
it { is_expected.to be_valid }
|
|
end
|
|
|
|
context "when domains including keys and certificates are associated" do
|
|
before do
|
|
allow(project)
|
|
.to receive(:pages_domains)
|
|
.and_return([instance_double(PagesDomain, https?: true)])
|
|
end
|
|
|
|
it { is_expected.to be_valid }
|
|
end
|
|
|
|
context "when domains including no keys or certificates are associated" do
|
|
before do
|
|
allow(project)
|
|
.to receive(:pages_domains)
|
|
.and_return([instance_double(PagesDomain, https?: false)])
|
|
end
|
|
|
|
it { is_expected.not_to be_valid }
|
|
end
|
|
end
|
|
|
|
describe '#toggle_ci_cd_settings!' do
|
|
it 'toggles the value on #settings' do
|
|
project = create(:project, group_runners_enabled: false)
|
|
|
|
expect(project.group_runners_enabled).to be false
|
|
|
|
project.toggle_ci_cd_settings!(:group_runners_enabled)
|
|
|
|
expect(project.group_runners_enabled).to be true
|
|
end
|
|
end
|
|
|
|
describe '#gitlab_deploy_token' do
|
|
let(:project) { create(:project) }
|
|
|
|
subject { project.gitlab_deploy_token }
|
|
|
|
context 'when there is a gitlab deploy token associated' do
|
|
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) }
|
|
|
|
it { is_expected.to eq(deploy_token) }
|
|
end
|
|
|
|
context 'when there is no a gitlab deploy token associated' do
|
|
it { is_expected.to be_nil }
|
|
end
|
|
|
|
context 'when there is a gitlab deploy token associated but is has been revoked' do
|
|
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :revoked, projects: [project]) }
|
|
it { is_expected.to be_nil }
|
|
end
|
|
|
|
context 'when there is a gitlab deploy token associated but it is expired' do
|
|
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :expired, projects: [project]) }
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
|
|
context 'when there is a deploy token associated with a different name' do
|
|
let!(:deploy_token) { create(:deploy_token, projects: [project]) }
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
|
|
context 'when there is a deploy token associated to a different project' do
|
|
let(:project_2) { create(:project) }
|
|
let!(:deploy_token) { create(:deploy_token, projects: [project_2]) }
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
end
|
|
|
|
context 'with uploads' do
|
|
it_behaves_like 'model with mounted uploader', true do
|
|
let(:model_object) { create(:project, :with_avatar) }
|
|
let(:upload_attribute) { :avatar }
|
|
let(:uploader_class) { AttachmentUploader }
|
|
end
|
|
end
|
|
|
|
context '#commits_by' do
|
|
let(:project) { create(:project, :repository) }
|
|
let(:commits) { project.repository.commits('HEAD', limit: 3).commits }
|
|
let(:commit_shas) { commits.map(&:id) }
|
|
|
|
it 'retrieves several commits from the repository by oid' do
|
|
expect(project.commits_by(oids: commit_shas)).to eq commits
|
|
end
|
|
end
|
|
|
|
context '#members_among' do
|
|
let(:users) { create_list(:user, 3) }
|
|
set(:group) { create(:group) }
|
|
set(:project) { create(:project, namespace: group) }
|
|
|
|
before do
|
|
project.add_guest(users.first)
|
|
project.group.add_maintainer(users.last)
|
|
end
|
|
|
|
context 'when users is an Array' do
|
|
it 'returns project members among the users' do
|
|
expect(project.members_among(users)).to eq([users.first, users.last])
|
|
end
|
|
|
|
it 'maintains input order' do
|
|
expect(project.members_among(users.reverse)).to eq([users.last, users.first])
|
|
end
|
|
|
|
it 'returns empty array if users is empty' do
|
|
result = project.members_among([])
|
|
|
|
expect(result).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when users is a relation' do
|
|
it 'returns project members among the users' do
|
|
result = project.members_among(User.where(id: users.map(&:id)))
|
|
|
|
expect(result).to be_a(ActiveRecord::Relation)
|
|
expect(result).to eq([users.first, users.last])
|
|
end
|
|
|
|
it 'returns empty relation if users is empty' do
|
|
result = project.members_among(User.none)
|
|
|
|
expect(result).to be_a(ActiveRecord::Relation)
|
|
expect(result).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#find_or_initialize_services" do
|
|
subject { build(:project) }
|
|
|
|
it 'returns only enabled services' do
|
|
allow(Service).to receive(:available_services_names).and_return(%w(prometheus pushover))
|
|
allow(subject).to receive(:disabled_services).and_return(%w(prometheus))
|
|
|
|
services = subject.find_or_initialize_services
|
|
|
|
expect(services.count).to eq 1
|
|
expect(services).to include(PushoverService)
|
|
end
|
|
end
|
|
|
|
describe "#find_or_initialize_service" do
|
|
subject { build(:project) }
|
|
|
|
it 'avoids N+1 database queries' do
|
|
allow(Service).to receive(:available_services_names).and_return(%w(prometheus pushover))
|
|
|
|
control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_service('prometheus') }.count
|
|
|
|
allow(Service).to receive(:available_services_names).and_call_original
|
|
|
|
expect { subject.find_or_initialize_service('prometheus') }.not_to exceed_query_limit(control_count)
|
|
end
|
|
|
|
it 'returns nil if service is disabled' do
|
|
allow(subject).to receive(:disabled_services).and_return(%w(prometheus))
|
|
|
|
expect(subject.find_or_initialize_service('prometheus')).to be_nil
|
|
end
|
|
end
|
|
|
|
describe '.find_without_deleted' do
|
|
it 'returns nil if the project is about to be removed' do
|
|
project = create(:project, pending_delete: true)
|
|
|
|
expect(described_class.find_without_deleted(project.id)).to be_nil
|
|
end
|
|
|
|
it 'returns a project when it is not about to be removed' do
|
|
project = create(:project)
|
|
|
|
expect(described_class.find_without_deleted(project.id)).to eq(project)
|
|
end
|
|
end
|
|
|
|
describe '.for_group' do
|
|
it 'returns the projects for a given group' do
|
|
group = create(:group)
|
|
project = create(:project, namespace: group)
|
|
|
|
expect(described_class.for_group(group)).to eq([project])
|
|
end
|
|
end
|
|
|
|
describe '.deployments' do
|
|
subject { project.deployments }
|
|
|
|
let(:project) { create(:project) }
|
|
|
|
before do
|
|
allow_any_instance_of(Deployment).to receive(:create_ref)
|
|
end
|
|
|
|
context 'when there is a deployment record with created status' do
|
|
let(:deployment) { create(:deployment, :created, project: project) }
|
|
|
|
it 'does not return the record' do
|
|
is_expected.to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when there is a deployment record with running status' do
|
|
let(:deployment) { create(:deployment, :running, project: project) }
|
|
|
|
it 'does not return the record' do
|
|
is_expected.to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when there is a deployment record with success status' do
|
|
let(:deployment) { create(:deployment, :success, project: project) }
|
|
|
|
it 'returns the record' do
|
|
is_expected.to eq([deployment])
|
|
end
|
|
end
|
|
end
|
|
|
|
def rugged_config
|
|
rugged_repo(project.repository).config
|
|
end
|
|
end
|