diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index b056b50d16d9dd0f4c5d2e1209822bc235958a95..774de53928981a3d126448dcf1c4798ed5b76d3c 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -25481,6 +25481,10 @@ four standard [pagination arguments](#pagination-arguments): Cluster agents in the namespace with remote development capabilities. +DETAILS: +**Deprecated** in GitLab 17.8. +Use `workspacesClusterAgents`. + Returns [`ClusterAgentConnection`](#clusteragentconnection). This field returns a [connection](#connections). It accepts the @@ -25966,6 +25970,26 @@ four standard [pagination arguments](#pagination-arguments): | `subscribed` **{warning-solid}** | [`SubscriptionStatus`](#subscriptionstatus) | **Introduced** in GitLab 17.5. **Status**: Experiment. Work items the current user is subscribed to. Is ignored if `filter_subscriptions` feature flag is disabled. | | `types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. | +##### `Group.workspacesClusterAgents` + +Cluster agents in the namespace with workspaces capabilities. + +DETAILS: +**Introduced** in GitLab 17.8. +**Status**: Experiment. + +Returns [`ClusterAgentConnection`](#clusteragentconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#pagination-arguments): +`before: String`, `after: String`, `first: Int`, and `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `filter` | [`NamespaceClusterAgentFilter!`](#namespaceclusteragentfilter) | Filter the types of cluster agents to return. | + ### `GroupAuditEventNamespaceFilter` Represents a subgroup or project filter that belongs to a group level external audit event streaming destination. @@ -29634,6 +29658,10 @@ four standard [pagination arguments](#pagination-arguments): Cluster agents in the namespace with remote development capabilities. +DETAILS: +**Deprecated** in GitLab 17.8. +Use `workspacesClusterAgents`. + Returns [`ClusterAgentConnection`](#clusteragentconnection). This field returns a [connection](#connections). It accepts the @@ -29763,6 +29791,26 @@ four standard [pagination arguments](#pagination-arguments): | ---- | ---- | ----------- | | `name` | [`IssueType`](#issuetype) | Filter work item types by the given name. | +##### `Namespace.workspacesClusterAgents` + +Cluster agents in the namespace with workspaces capabilities. + +DETAILS: +**Introduced** in GitLab 17.8. +**Status**: Experiment. + +Returns [`ClusterAgentConnection`](#clusteragentconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#pagination-arguments): +`before: String`, `after: String`, `first: Int`, and `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `filter` | [`NamespaceClusterAgentFilter!`](#namespaceclusteragentfilter) | Filter the types of cluster agents to return. | + ### `NamespaceBan` #### Fields diff --git a/ee/app/finders/remote_development/cluster_agents_finder.rb b/ee/app/finders/remote_development/cluster_agents_finder.rb index 7eedf70acce6c4f419a8ae82e3182a292f066bc3..892ad043265d3d771cef6e272c5a94cdcb7eec70 100644 --- a/ee/app/finders/remote_development/cluster_agents_finder.rb +++ b/ee/app/finders/remote_development/cluster_agents_finder.rb @@ -11,7 +11,7 @@ def self.execute(namespace:, filter:, user:) def self.fetch_agents(filter, namespace, user) case filter when :unmapped - validate_user_can_read_namespace_agent_mappings!(user: user, namespace: namespace) + return Clusters::Agent.none unless user_can_read_namespace_agent_mappings?(user: user, namespace: namespace) # noinspection RailsParamDefResolve -- A symbol is a valid argument for 'select' existing_mapped_agents = @@ -26,7 +26,7 @@ def self.fetch_agents(filter, namespace, user) namespace.cluster_agents.id_not_in(existing_mapped_agents) when :directly_mapped - validate_user_can_read_namespace_agent_mappings!(user: user, namespace: namespace) + return Clusters::Agent.none unless user_can_read_namespace_agent_mappings?(user: user, namespace: namespace) relevant_mappings = RemoteDevelopmentNamespaceClusterAgentMapping.for_namespaces([namespace.id]) relevant_mappings = @@ -48,11 +48,8 @@ def self.fetch_agents(filter, namespace, user) end end - def self.validate_user_can_read_namespace_agent_mappings!(user:, namespace:) - raise Gitlab::Access::AccessDeniedError unless user.can?( - :read_remote_development_cluster_agent_mapping, - namespace - ) + def self.user_can_read_namespace_agent_mappings?(user:, namespace:) + user.can?(:read_remote_development_cluster_agent_mapping, namespace) end end end diff --git a/ee/app/graphql/ee/types/clusters/agent_type.rb b/ee/app/graphql/ee/types/clusters/agent_type.rb index 7037ba7b7eef047eb5a2f1655661cc9d6a85673a..ea5a9f524e8c4e1a982b96e7a2f5b15adb439930 100644 --- a/ee/app/graphql/ee/types/clusters/agent_type.rb +++ b/ee/app/graphql/ee/types/clusters/agent_type.rb @@ -26,7 +26,7 @@ module AgentType field :workspaces, ::Types::RemoteDevelopment::WorkspaceType.connection_type, null: true, - resolver: ::Resolvers::RemoteDevelopment::WorkspacesForAgentResolver, + resolver: ::Resolvers::RemoteDevelopment::ClusterAgent::WorkspacesResolver, description: 'Workspaces associated with the agent.' field :remote_development_agent_config, @@ -34,7 +34,7 @@ module AgentType extras: [:lookahead], null: true, description: 'Remote development agent config for the cluster agent.', - resolver: ::Resolvers::RemoteDevelopment::RemoteDevelopmentAgentConfigForAgentResolver, + resolver: ::Resolvers::RemoteDevelopment::ClusterAgent::RemoteDevelopmentAgentConfigResolver, experiment: { milestone: '17.4' } field :workspaces_agent_config, @@ -42,7 +42,7 @@ module AgentType extras: [:lookahead], null: true, description: 'Workspaces agent config for the cluster agent.', - resolver: ::Resolvers::RemoteDevelopment::WorkspacesAgentConfigForAgentResolver, + resolver: ::Resolvers::RemoteDevelopment::ClusterAgent::WorkspacesAgentConfigResolver, experiment: { milestone: '17.4' } def url_configurations diff --git a/ee/app/graphql/ee/types/current_user_type.rb b/ee/app/graphql/ee/types/current_user_type.rb index 71c5e0d0f36d81a76d5d10ee3a0e249a52d8bc29..f16a35e5342bb88ad5a5651cf7ac3b879c07ad51 100644 --- a/ee/app/graphql/ee/types/current_user_type.rb +++ b/ee/app/graphql/ee/types/current_user_type.rb @@ -9,7 +9,7 @@ module CurrentUserType prepended do field :workspaces, description: 'Workspaces owned by the current user.', - resolver: ::Resolvers::RemoteDevelopment::WorkspacesForCurrentUserResolver + resolver: ::Resolvers::RemoteDevelopment::WorkspacesResolver field :duo_chat_available, ::GraphQL::Types::Boolean, resolver: ::Resolvers::Ai::UserChatAccessResolver, diff --git a/ee/app/graphql/ee/types/namespace_type.rb b/ee/app/graphql/ee/types/namespace_type.rb index 8a888ad6d33d81602da224c0ed51a77a4d92617d..4d2184909a456eb091c502812f40be20c053d4c2 100644 --- a/ee/app/graphql/ee/types/namespace_type.rb +++ b/ee/app/graphql/ee/types/namespace_type.rb @@ -136,10 +136,11 @@ module NamespaceType field :remote_development_cluster_agents, ::Types::Clusters::AgentType.connection_type, + deprecated: { reason: 'Use `workspacesClusterAgents`', milestone: '17.8' }, extras: [:lookahead], null: true, description: 'Cluster agents in the namespace with remote development capabilities', - resolver: ::Resolvers::RemoteDevelopment::AgentsForNamespaceResolver + resolver: ::Resolvers::RemoteDevelopment::Namespace::ClusterAgentsResolver field :subscription_history, ::Types::GitlabSubscriptions::SubscriptionHistoryType.connection_type, @@ -147,6 +148,15 @@ module NamespaceType description: 'Find subscription history records.', experiment: { milestone: '17.3' }, method: :gitlab_subscription_histories + + field :workspaces_cluster_agents, + ::Types::Clusters::AgentType.connection_type, + extras: [:lookahead], + null: true, + description: 'Cluster agents in the namespace with workspaces capabilities', + experiment: { milestone: '17.8' }, + resolver: ::Resolvers::RemoteDevelopment::Namespace::ClusterAgentsResolver + def product_analytics_stored_events_limit object.root_ancestor.product_analytics_stored_events_limit end diff --git a/ee/app/graphql/ee/types/query_type.rb b/ee/app/graphql/ee/types/query_type.rb index a59094139f785dd5bda8039d4e45ff5065051bdf..4b9aa30bd8b638eef86cf748c20a9f81ee6d9a17 100644 --- a/ee/app/graphql/ee/types/query_type.rb +++ b/ee/app/graphql/ee/types/query_type.rb @@ -98,7 +98,7 @@ module QueryType field :workspaces, ::Types::RemoteDevelopment::WorkspaceType.connection_type, null: true, - resolver: ::Resolvers::RemoteDevelopment::WorkspacesForQueryRootResolver, + resolver: ::Resolvers::RemoteDevelopment::AdminWorkspacesResolver, description: 'Find workspaces across the entire instance. This field is only available to instance admins, ' \ 'it will return an empty result for all non-admins.' field :instance_external_audit_event_destinations, diff --git a/ee/app/graphql/ee/types/user_type.rb b/ee/app/graphql/ee/types/user_type.rb index b2d035b5437397aa142653e2c30910c7bd424a53..e9b815c09d08363df51e51350563eb2694e63371 100644 --- a/ee/app/graphql/ee/types/user_type.rb +++ b/ee/app/graphql/ee/types/user_type.rb @@ -9,7 +9,7 @@ module UserType prepended do field :workspaces, description: 'Workspaces owned by the current user.', - resolver: ::Resolvers::RemoteDevelopment::WorkspacesForCurrentUserResolver + resolver: ::Resolvers::RemoteDevelopment::WorkspacesResolver end end end diff --git a/ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver.rb b/ee/app/graphql/resolvers/remote_development/admin_workspaces_resolver.rb similarity index 98% rename from ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver.rb rename to ee/app/graphql/resolvers/remote_development/admin_workspaces_resolver.rb index 6009e607f052d9e3899cb43ab99575cfea660dfc..ff856113f963775ea48e486418e0b85e06f95931 100644 --- a/ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver.rb +++ b/ee/app/graphql/resolvers/remote_development/admin_workspaces_resolver.rb @@ -2,7 +2,7 @@ module Resolvers module RemoteDevelopment - class WorkspacesForQueryRootResolver < ::Resolvers::BaseResolver + class AdminWorkspacesResolver < ::Resolvers::BaseResolver include ResolvesIds # NOTE: We are intentionally not including Gitlab::Graphql::Authorize::AuthorizeResource, because this resolver diff --git a/ee/app/graphql/resolvers/remote_development/agents_for_namespace_resolver.rb b/ee/app/graphql/resolvers/remote_development/agents_for_namespace_resolver.rb deleted file mode 100644 index d67df22afa414524cee822a3c693d3887c6f58f1..0000000000000000000000000000000000000000 --- a/ee/app/graphql/resolvers/remote_development/agents_for_namespace_resolver.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Resolvers - module RemoteDevelopment - class AgentsForNamespaceResolver < ::Resolvers::BaseResolver - include Gitlab::Graphql::Authorize::AuthorizeResource - - type Types::Clusters::AgentType.connection_type, null: true - - argument :filter, Types::RemoteDevelopment::NamespaceClusterAgentFilterEnum, - required: true, - description: 'Filter the types of cluster agents to return.' - - def resolve(**args) - unless License.feature_available?(:remote_development) - raise_resource_not_available_error! "'remote_development' licensed feature is not available" - end - - raise_resource_not_available_error! unless @object.group_namespace? - - ::RemoteDevelopment::ClusterAgentsFinder.execute( - namespace: @object, - filter: args[:filter].downcase.to_sym, - user: current_user - ) - rescue Gitlab::Access::AccessDeniedError - raise_resource_not_available_error! - end - end - end -end diff --git a/ee/app/graphql/resolvers/remote_development/cluster_agent/remote_development_agent_config_resolver.rb b/ee/app/graphql/resolvers/remote_development/cluster_agent/remote_development_agent_config_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..46a91d17d4ce33f7d3377b0b1f094f94219991dd --- /dev/null +++ b/ee/app/graphql/resolvers/remote_development/cluster_agent/remote_development_agent_config_resolver.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# TODO: clusterAgent.remoteDevelopmentAgentConfig GraphQL is deprecated - remove in 17.10 - https://gitlab.com/gitlab-org/gitlab/-/issues/480769 +module Resolvers + module RemoteDevelopment + module ClusterAgent + class RemoteDevelopmentAgentConfigResolver < ::Resolvers::BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include LooksAhead + + type Types::RemoteDevelopment::RemoteDevelopmentAgentConfigType, null: true + + alias_method :agent, :object + + # @param [Hash] _args Not used + # @return [RemoteDevelopmentAgentConfig] + def resolve_with_lookahead(**_args) + unless License.feature_available?(:remote_development) + raise_resource_not_available_error! "'remote_development' licensed feature is not available" + end + + raise Gitlab::Access::AccessDeniedError unless can_read_remote_development_agent_config? + + BatchLoader::GraphQL.for(agent.id).batch do |agent_ids, loader| + agent_configs = ::RemoteDevelopment::RemoteDevelopmentAgentConfigsFinder.execute( + current_user: current_user, + cluster_agent_ids: agent_ids + ) + apply_lookahead(agent_configs).each do |agent_config| + # noinspection RubyResolve -- https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-32301 + loader.call(agent_config.cluster_agent_id, agent_config) + end + end + end + + private + + # @return [TrueClass, FalseClass] + def can_read_remote_development_agent_config? + # noinspection RubyNilAnalysis - This is because the superclass #current_user uses #[], which can return nil + current_user.can?(:read_cluster_agent, agent) + end + end + end + end +end diff --git a/ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_agent_config_resolver.rb b/ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_agent_config_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..908f9dc7a5d70652ae5c27f0b83b567cf5ef6edf --- /dev/null +++ b/ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_agent_config_resolver.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Resolvers + module RemoteDevelopment + module ClusterAgent + class WorkspacesAgentConfigResolver < ::Resolvers::BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include LooksAhead + + type Types::RemoteDevelopment::WorkspacesAgentConfigType, null: true + + alias_method :agent, :object + + # @param [Hash] _args Not used + # @return [WorkspacesAgentConfig] + def resolve_with_lookahead(**_args) + unless License.feature_available?(:remote_development) + raise_resource_not_available_error! "'remote_development' licensed feature is not available" + end + + raise Gitlab::Access::AccessDeniedError unless can_read_workspaces_agent_config? + + BatchLoader::GraphQL.for(agent.id).batch do |agent_ids, loader| + agent_configs = ::RemoteDevelopment::AgentConfigsFinder.execute( + current_user: current_user, + cluster_agent_ids: agent_ids + ) + apply_lookahead(agent_configs).each do |agent_config| + # noinspection RubyResolve -- https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-32301 + loader.call(agent_config.cluster_agent_id, agent_config) + end + end + end + + private + + # @return [TrueClass, FalseClass] + def can_read_workspaces_agent_config? + # noinspection RubyNilAnalysis - This is because the superclass #current_user uses #[], which can return nil + current_user.can?(:read_cluster_agent, agent) + end + end + end + end +end diff --git a/ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_resolver.rb b/ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..68c04db7e4506d429e7f0c9782f0cec655d430d8 --- /dev/null +++ b/ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_resolver.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Resolvers + module RemoteDevelopment + module ClusterAgent + class WorkspacesResolver < ::Resolvers::BaseResolver + include ResolvesIds + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::RemoteDevelopment::WorkspaceType.connection_type, null: true + authorize :admin_cluster + authorizes_object! + + argument :ids, [::Types::GlobalIDType[::RemoteDevelopment::Workspace]], + required: false, + description: + 'Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`.' + + argument :project_ids, [::Types::GlobalIDType[Project]], + required: false, + description: 'Filter workspaces by project GlobalID.' + + argument :actual_states, [GraphQL::Types::String], + required: false, + description: 'Filter workspaces by actual states.' + + alias_method :agent, :object + + def resolve(**args) + unless License.feature_available?(:remote_development) + raise_resource_not_available_error! "'remote_development' licensed feature is not available" + end + + ::RemoteDevelopment::WorkspacesFinder.execute( + current_user: current_user, + agent_ids: [agent.id], + ids: resolve_ids(args[:ids]).map(&:to_i), + project_ids: resolve_ids(args[:project_ids]).map(&:to_i), + actual_states: args[:actual_states] || [] + ) + end + end + end + end +end diff --git a/ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb b/ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..1ee5baad49f90526709eb7b3ca323f42c6541d84 --- /dev/null +++ b/ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Resolvers + module RemoteDevelopment + module Namespace + class ClusterAgentsResolver < ::Resolvers::BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::Clusters::AgentType.connection_type, null: true + + argument :filter, Types::RemoteDevelopment::NamespaceClusterAgentFilterEnum, + required: true, + description: 'Filter the types of cluster agents to return.' + + def resolve(**args) + unless License.feature_available?(:remote_development) + raise_resource_not_available_error! "'remote_development' licensed feature is not available" + end + + raise_resource_not_available_error! unless @object.group_namespace? + + ::RemoteDevelopment::ClusterAgentsFinder.execute( + namespace: @object, + filter: args[:filter].downcase.to_sym, + user: current_user + ) + end + end + end + end +end diff --git a/ee/app/graphql/resolvers/remote_development/remote_development_agent_config_for_agent_resolver.rb b/ee/app/graphql/resolvers/remote_development/remote_development_agent_config_for_agent_resolver.rb deleted file mode 100644 index 771f6fa669e8e44c60f96dc0f3c58de0d4d5a4e0..0000000000000000000000000000000000000000 --- a/ee/app/graphql/resolvers/remote_development/remote_development_agent_config_for_agent_resolver.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -# TODO: clusterAgent.remoteDevelopmentAgentConfig GraphQL is deprecated - remove in 17.10 - https://gitlab.com/gitlab-org/gitlab/-/issues/480769 -module Resolvers - module RemoteDevelopment - class RemoteDevelopmentAgentConfigForAgentResolver < ::Resolvers::BaseResolver - include Gitlab::Graphql::Authorize::AuthorizeResource - include LooksAhead - - type Types::RemoteDevelopment::RemoteDevelopmentAgentConfigType, null: true - - alias_method :agent, :object - - # - # Resolve the remote development agent config for the given agent. - # - # @param [Hash] **_args The arguments passed to the resolver, and do not in use here - # - # @return [RemoteDevelopmentAgentConfig] The remote development agent config for the given agent - # - def resolve_with_lookahead(**_args) - unless License.feature_available?(:remote_development) - raise_resource_not_available_error! "'remote_development' licensed feature is not available" - end - - raise Gitlab::Access::AccessDeniedError unless can_read_remote_development_agent_config? - - BatchLoader::GraphQL.for(agent.id).batch do |agent_ids, loader| - agent_configs = ::RemoteDevelopment::RemoteDevelopmentAgentConfigsFinder.execute( - current_user: current_user, - cluster_agent_ids: agent_ids - ) - apply_lookahead(agent_configs).each do |agent_config| - # noinspection RubyResolve -- https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-32301 - loader.call(agent_config.cluster_agent_id, agent_config) - end - end - end - - private - - def can_read_remote_development_agent_config? - # noinspection RubyNilAnalysis - This is because the superclass #current_user uses #[], which can return nil - current_user.can?(:read_cluster_agent, agent) - end - end - end -end diff --git a/ee/app/graphql/resolvers/remote_development/workspaces_agent_config_for_agent_resolver.rb b/ee/app/graphql/resolvers/remote_development/workspaces_agent_config_for_agent_resolver.rb deleted file mode 100644 index 5ff7a6388851dc4c377a2a4ac7655d205f72c2d5..0000000000000000000000000000000000000000 --- a/ee/app/graphql/resolvers/remote_development/workspaces_agent_config_for_agent_resolver.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Resolvers - module RemoteDevelopment - class WorkspacesAgentConfigForAgentResolver < ::Resolvers::BaseResolver - include Gitlab::Graphql::Authorize::AuthorizeResource - include LooksAhead - - type Types::RemoteDevelopment::WorkspacesAgentConfigType, null: true - - alias_method :agent, :object - - # - # Resolve the workspaces agent config for the given agent. - # - # @param [Hash] **_args The arguments passed to the resolver, and do not in use here - # - # @return [WorkspacesAgentConfig] The workspaces agent config for the given agent - # - def resolve_with_lookahead(**_args) - unless License.feature_available?(:remote_development) - raise_resource_not_available_error! "'remote_development' licensed feature is not available" - end - - raise Gitlab::Access::AccessDeniedError unless can_read_workspaces_agent_config? - - BatchLoader::GraphQL.for(agent.id).batch do |agent_ids, loader| - agent_configs = ::RemoteDevelopment::AgentConfigsFinder.execute( - current_user: current_user, - cluster_agent_ids: agent_ids - ) - apply_lookahead(agent_configs).each do |agent_config| - # noinspection RubyResolve -- https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-32301 - loader.call(agent_config.cluster_agent_id, agent_config) - end - end - end - - private - - def can_read_workspaces_agent_config? - # noinspection RubyNilAnalysis - This is because the superclass #current_user uses #[], which can return nil - current_user.can?(:read_cluster_agent, agent) - end - end - end -end diff --git a/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb b/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb deleted file mode 100644 index 880535807c02d9d399ba1806b2600b57a32f886e..0000000000000000000000000000000000000000 --- a/ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module Resolvers - module RemoteDevelopment - class WorkspacesForAgentResolver < ::Resolvers::BaseResolver - include ResolvesIds - include Gitlab::Graphql::Authorize::AuthorizeResource - - type Types::RemoteDevelopment::WorkspaceType.connection_type, null: true - authorize :admin_cluster - authorizes_object! - - argument :ids, [::Types::GlobalIDType[::RemoteDevelopment::Workspace]], - required: false, - description: - 'Filter workspaces by workspace GlobalIDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`.' - - argument :project_ids, [::Types::GlobalIDType[Project]], - required: false, - description: 'Filter workspaces by project GlobalID.' - - argument :actual_states, [GraphQL::Types::String], - required: false, - description: 'Filter workspaces by actual states.' - - alias_method :agent, :object - - def resolve(**args) - unless License.feature_available?(:remote_development) - raise_resource_not_available_error! "'remote_development' licensed feature is not available" - end - - ::RemoteDevelopment::WorkspacesFinder.execute( - current_user: current_user, - agent_ids: [agent.id], - ids: resolve_ids(args[:ids]).map(&:to_i), - project_ids: resolve_ids(args[:project_ids]).map(&:to_i), - actual_states: args[:actual_states] || [] - ) - end - end - end -end diff --git a/ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver.rb b/ee/app/graphql/resolvers/remote_development/workspaces_resolver.rb similarity index 96% rename from ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver.rb rename to ee/app/graphql/resolvers/remote_development/workspaces_resolver.rb index 1d79ba49357c7b6f7e13e69aafdb094ac29d79ff..c8fd4f3d76efee4d10af6fe1c8a306c09ee20660 100644 --- a/ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver.rb +++ b/ee/app/graphql/resolvers/remote_development/workspaces_resolver.rb @@ -2,7 +2,7 @@ module Resolvers module RemoteDevelopment - class WorkspacesForCurrentUserResolver < ::Resolvers::BaseResolver + class WorkspacesResolver < ::Resolvers::BaseResolver include ResolvesIds include Gitlab::Graphql::Authorize::AuthorizeResource diff --git a/ee/lib/remote_development/README.md b/ee/lib/remote_development/README.md index cc126c83e749466fd3e7be3810ca45f58c96d631..d04fa9b138a3298a20f40e5df437b1d6aff9d874 100644 --- a/ee/lib/remote_development/README.md +++ b/ee/lib/remote_development/README.md @@ -530,6 +530,7 @@ However, when exercising the backend/fullstack code where the business logic liv - Unit Tests: This lowest level of testing, and only responsible for testing the logic directly contained in the class under test. - not-quite-unit tests (e.g. GraphQL tests): Sometimes we test groups of collaborating classes, and do not have direct unit tests. For example, GraphQL mutations and resolvers are tested at the `spec/requests` level, and exercise not just the resolver, but also the relevant finder and authorization policy code. This follows the [standard GitLab recommendations for testing resolvers](https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#testing) + See [`ee/spec/requests/api/graphql/remote_development/README.md`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/spec/requests/api/graphql/remote_development/README.md) for more details on the GraphQL request specs. - ROP main-method intergration tests: These test the behavior of a single entire ROP chain via the main method entry point. They are a good balance of speed and thorough integration coverage of the domain logic implemented by the ROP chain. Individual ROP steps leverage `fast_spec_helper` and use `instance_double` to mock db models, this prevents coupling to Rails. These main-method integration tests use real DB models and allow us to catch edge cases or false positive tests due to incorrect or outdated mocks from unit tests using `instance_double`. - Request Integration Test: This spec is [at `ee/spec/requests/remote_development/integration_spec.rb`](../../spec/requests/remote_development/integration_spec.rb). It serves to fully exercise all of the Rails stack logic, as a near-the-top-of-the-testing-pyramid, happy-path scenario testing of the full lifecycle of creating a workspace. It uses the same use-case and scenario as the Feature Testing specs, except that it does not exercise the Web UI. It mocks out the agent reconciliation requests. This means that it is a "sweet spot" for integration testing because it is comprehensive, while still remaining both fast and reliable (compared to other integration-level specs which involve the web UI). Thus, we should prefer this spec to add the majority of our happy-path integration testing coverage. diff --git a/ee/spec/finders/remote_development/cluster_agents_finder_spec.rb b/ee/spec/finders/remote_development/cluster_agents_finder_spec.rb index 169ec5a1d4d5e8d9193d96c70c6f563c1f596f81..3ff788097fbdd467c6fc611c90f8e9d1e8d521c7 100644 --- a/ee/spec/finders/remote_development/cluster_agents_finder_spec.rb +++ b/ee/spec/finders/remote_development/cluster_agents_finder_spec.rb @@ -131,8 +131,8 @@ context 'when user does not have adequate permissions' do let(:user) { developer } - it 'raises an AccessDeniedError' do - expect { response }.to raise_error(Gitlab::Access::AccessDeniedError) + it 'returns an empty response' do + expect(response).to eq([]) end end end @@ -158,8 +158,8 @@ context 'when user does not have adequate permissions' do let(:user) { developer } - it 'raises an AccessDeniedError' do - expect { response }.to raise_error(Gitlab::Access::AccessDeniedError) + it 'returns an empty response' do + expect(response).to eq([]) end end end diff --git a/ee/spec/graphql/types/current_user_type_spec.rb b/ee/spec/graphql/types/current_user_type_spec.rb index eaeefd57c354b517396f500df38941317ffc8a6f..cd268975d487e1797fa64628be3ef2b118f16389 100644 --- a/ee/spec/graphql/types/current_user_type_spec.rb +++ b/ee/spec/graphql/types/current_user_type_spec.rb @@ -16,7 +16,7 @@ it 'returns workspaces' do is_expected.to have_graphql_type(Types::RemoteDevelopment::WorkspaceType.connection_type) - is_expected.to have_graphql_resolver(Resolvers::RemoteDevelopment::WorkspacesForCurrentUserResolver) + is_expected.to have_graphql_resolver(Resolvers::RemoteDevelopment::WorkspacesResolver) end end end diff --git a/ee/spec/requests/api/graphql/remote_development/README.md b/ee/spec/requests/api/graphql/remote_development/README.md index 40b18a371f83643d98449f4c62ef91a1293f4c3e..b7e0d69d659c31fa69d031e9f37335f50ccf5e72 100644 --- a/ee/spec/requests/api/graphql/remote_development/README.md +++ b/ee/spec/requests/api/graphql/remote_development/README.md @@ -1,20 +1,61 @@ -NOTE: The directory structure of this folder mirrors the structure of the GraphQL API schema +# Workspaces GraphQL API resolver request integration specs + +## Directory structure + +The directory structure of this folder mirrors the structure of the GraphQL API schema under the root `Query` type. -For example: +Each GraphQL field and its corresponding resolver has a corresponding spec folder containing specs which test the related resolver+finder functionality. -- `ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces` - contains specs which test the `Query.project.clusterAgent.workspaces` field in the GraphQL API schema, - as well as any other field which contains a `clusterAgent` field or collection. -- `ee/spec/requests/api/graphql/remote_development/current_user/workspaces` - contains specs which test the `Query.currentUser.workspaces` field in the GraphQL API schema. -- `ee/spec/requests/api/graphql/remote_development/workspace` - contains specs which test the `Query.workspace` field in the GraphQL API schema. -- `ee/spec/requests/api/graphql/remote_development/workspaces` - contains specs which test the `Query.workspaces` field in the GraphQL API schema (note that - only admins may use this field). +Note that some entries may be reachable by other GraphQL query traversal paths, e.g., a `ClusterAgent` is reachable via `Query.project.clusterAgent...` and `Query.group.clusterAgents...`. In these cases, a single representatative example query is used in the specs, since the path used to reach a GraphQL node has no impact on the functionality of the nested fields and their resolvers. -If you add new spec files, you should update `tests.yml` and `scripts/verify-tff-mapping` accordingly. +Here are the related spec folders for the fields (in alphabetical order by resolver source file path) + +- GraphQL Field: `Query.workspaces` + - Spec folder: `ee/spec/requests/api/graphql/remote_development/workspaces` + - API docs: https://docs.gitlab.com/ee/api/graphql/reference/index.html#queryworkspaces + - Resolver source file for `tests.yml` and `verify-tff-mapping`: `ee/app/graphql/resolvers/remote_development/admin_workspaces_resolver.rb` + - Notes: Only admins may use this field. + +- GraphQL Field: `Query.project.clusterAgent.remoteDevelopmentAgentConfig` + - Spec folder: `ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config` + - API docs: https://docs.gitlab.com/ee/api/graphql/reference/index.html#clusteragentremotedevelopmentagentconfig + - Resolver source file for `tests.yml` and `verify-tff-mapping`: `ee/app/graphql/resolvers/remote_development/cluster_agent/remote_development_agent_config_resolver.rb` + - Note: THIS FIELD IS DEPRECATED, AND WILL BE REMOVED IN A FUTURE RELEASE. + +- Field: `Query.project.clusterAgent.workspaces` + - Spec folder: `ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces` + - API docs: https://docs.gitlab.com/ee/api/graphql/reference/index.html#clusteragentworkspaces + - Resolver source file for `tests.yml` and `verify-tff-mapping`: `ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces.rb` + +- GraphQL Field: `Query.project.clusterAgent.workspacesAgentConfig` + - Spec folder: `ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config` + - API docs: https://docs.gitlab.com/ee/api/graphql/reference/index.html#clusteragentworkspacesagentconfig + - Resolver source file for `tests.yml` and `verify-tff-mapping`: `ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_agent_config_resolver.rb` + +- GraphQL Field: `Query.namespace.remote_development_cluster_agents` + - Spec folder: `ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents` + - API docs: https://docs.gitlab.com/ee/api/graphql/reference/index.html#namespaceremotedevelopmentclusteragents + - Resolver source file for `tests.yml` and `verify-tff-mapping`: `ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb` + - Notes: This is the same resolver used by `Query.currentUser.namespace.workspaces_cluster_agents`. THIS FIELD IS DEPRECATED AND WILL BE REMOVED IN THE 18.0 RELEASE. + +- GraphQL Field: `Query.namespace.namespace.workspaces_cluster_agents` + - Spec folder: `ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents` + - API docs: https://docs.gitlab.com/ee/api/graphql/reference/index.html#namespaceworkspacesclusteragents + - Resolver source file for `tests.yml` and `verify-tff-mapping`: `ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb` + - Notes: This is the same resolver used by `Query.currentUser.namespace.remote_development_cluster_agents`. + +- GraphQL Field: `Query.workspace` + - Spec folder: `ee/spec/requests/api/graphql/remote_development/workspace` + - API docs: https://docs.gitlab.com/ee/api/graphql/reference/index.html#queryworkspace + - Resolver source file for `tests.yml` and `verify-tff-mapping`: `ee/app/graphql/resolvers/remote_development/namespace/workspaces_resolver.rb` + - Notes: This is the same resolver used by `Query.currentUser.workspaces` + +- GraphQL Field: `Query.currentUser.workspaces` + - Spec folder: `ee/spec/requests/api/graphql/remote_development/current_user/workspaces` + - API docs: https://docs.gitlab.com/ee/api/graphql/reference/index.html#currentuserworkspaces + - Resolver source file for `tests.yml` and `verify-tff-mapping`: `ee/app/graphql/resolvers/remote_development/workspaces_resolver.rb` + - Notes: This is the same resolver used by `Query.currentUser.workspace` The `shared.rb` file in the root contains RSpec shared contexts and examples used by all specs in this directory. @@ -22,4 +63,30 @@ specs in this directory. The `shared.rb` files in the subdirectories contain shared rspec contexts and examples specific to the query being tested. -This allows the individual spec files to be very DRY and cohesive, yet provide thorough coverage. +## These are kind of complex and hard to follow. Why? + +They do heavily leverage RSpec shared contexts and examples across multiple files, which requires more effort to understand how they work. + +However, this allows the individual spec files to be very DRY and cohesive, yet still provide thorough coverage across multiple aspects of behavior. + +Without this approach, achieving equivalent coverage across all of this same GraphQL API behavior would result in specs with significantly more verbosity and duplication. + +## Adding new spec files + +If you add new spec files, you should update `tests.yml` and `scripts/verify-tff-mapping` accordingly. + +## Why aren't all individual fields of graphql types tested in these specs? + +None of these other graphql API request specs test the actual fields because that’s not necessary. + +This is because between the finder specs, the GraphQL type specs, and the way GraphQL works +to automatically populate the fields into a type, the population of the individual fields in +the returned GraphQL objects is already adequately covered. + +Thus, the only things these request integration specs assert are the actual logic and behavior of the resolvers +and finders, and how they integrate: +- whether they process all the arguments correctly +- whether they return the right records or not +- whether they handle errors properly +- whether they do proper authorization +- etc. diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/TODO_DEPRECATED.md b/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/TODO_DEPRECATED.md new file mode 100644 index 0000000000000000000000000000000000000000..2fed024ee29ee6f0eeff6daccd6436d69b37016a --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/TODO_DEPRECATED.md @@ -0,0 +1,6 @@ +TODO: clusterAgent.remoteDevelopmentAgentConfig GraphQL is deprecated. + +Remove this entire folder `ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config` +and its contents in 18.0 + +https://gitlab.com/gitlab-org/gitlab/-/issues/509049 diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/shared.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/shared.rb new file mode 100644 index 0000000000000000000000000000000000000000..abd965c0dc7b7d33690d03eef368f12b2aedd807 --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/shared.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require_relative '../../shared' + +RSpec.shared_context 'for a Query.project.clusterAgent.remoteDevelopmentAgentConfig query' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :in_group) } + let_it_be(:agent) { create(:cluster_agent, project: project) } + let_it_be(:remote_development_agent_config) { create(:workspaces_agent_config, agent: agent) } + + let_it_be(:authorized_user) do + # create the minimum privileged user that should have the project and namespace + # permissions to access the remote_development_agent_config. + create(:user, developer_of: project.namespace) + end + + let_it_be(:unauthorized_user) do + # create the maximum privileged user that should NOT have the project and namespace + # permissions to access the agent. + create(:user, reporter_of: project.namespace) + end + + let_it_be(:unauthorized_remote_development_agent_config) { create(:workspaces_agent_config) } + + let(:args) { { full_path: project.full_path } } + let(:attributes) { { name: agent.name } } + let(:fields) do + query_graphql_field( + :cluster_agent, + attributes, + [ + query_graphql_field( + :remote_development_agent_config, + all_graphql_fields_for("remote_development_agent_configs".classify, max_depth: 1) + ) + ] + ) + end + + let(:query) { graphql_query_for(:project, args, fields) } + + subject(:actual_remote_development_agent_config) do + graphql_dig_at(graphql_data, :project, :clusterAgent, :remoteDevelopmentAgentConfig) + end +end + +RSpec.shared_examples 'single remoteDevelopmentAgentConfig query' do + include_context 'in licensed environment' + + context 'when user is authorized' do + include_context 'with authorized user as current user' + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns single remote_development_agent_config' + + context 'when the user requests a remote_development_agent_config that they are not authorized for' do + let(:agent) { unauthorized_remote_development_agent_config.agent } + let(:project) { agent.project } + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns blank' + end + end + + context 'when user is not authorized' do + include_context 'with unauthorized user as current user' + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns blank' + end + + it_behaves_like 'query in unlicensed environment' +end + +RSpec.shared_examples 'query returns single remote_development_agent_config' do + include GraphqlHelpers + + before do + post_graphql(query, current_user: current_user) + end + + it "returns correct object" do + expect(actual_remote_development_agent_config.fetch('id')) + .to eq("gid://gitlab/RemoteDevelopment::RemoteDevelopmentAgentConfig/#{remote_development_agent_config.id}") + end +end diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/with_no_args_spec.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/with_no_args_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..bdcab3be29559540fdf52bf0052979acf688eddb --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/with_no_args_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative './shared' + +# NOTE: Even though this `single remoteDevelopmentAgentConfig` spec only has no fields to test, we still use similar +# shared examples patterns and structure as the other multi-model query specs, for consistency. + +RSpec.describe 'Query.project.clusterAgent.remoteDevelopmentAgentConfig', feature_category: :workspaces do + include_context 'for a Query.project.clusterAgent.remoteDevelopmentAgentConfig query' + + it_behaves_like 'single remoteDevelopmentAgentConfig query' +end diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/shared.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/shared.rb index 3d5777fc65b7fdd633aeab94e82e1d1b16f73fea..91b15e500f4928d4a5b2b5cd059249f54735bb7e 100644 --- a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/shared.rb +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/shared.rb @@ -41,5 +41,5 @@ workspace.reload # Ensure loaded workspace fixture's agent reflects updated created_by_user end - subject { graphql_data.dig('project', 'clusterAgent', 'workspaces', 'nodes') } + subject(:actual_workspaces) { graphql_dig_at(graphql_data, :project, :clusterAgent, :workspaces, :nodes) } end diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/shared.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/shared.rb new file mode 100644 index 0000000000000000000000000000000000000000..d9c0f35dae601aca21eab7eaa9db084a675dceed --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/shared.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require_relative '../../shared' + +RSpec.shared_context 'for a Query.project.clusterAgent.workspacesAgentConfig query' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :in_group) } + let_it_be(:agent) { create(:cluster_agent, project: project) } + let_it_be(:workspaces_agent_config) { create(:workspaces_agent_config, agent: agent) } + + let_it_be(:authorized_user) do + # create the minimum privileged user that should have the project and namespace + # permissions to access the workspaces_agent_config. + create(:user, developer_of: project.namespace) + end + + let_it_be(:unauthorized_user) do + # create the maximum privileged user that should NOT have the project and namespace + # permissions to access the agent. + create(:user, reporter_of: project.namespace) + end + + let_it_be(:unauthorized_workspaces_agent_config) { create(:workspaces_agent_config) } + + let(:args) { { full_path: project.full_path } } + let(:attributes) { { name: agent.name } } + let(:fields) do + query_graphql_field( + :cluster_agent, + attributes, + [ + query_graphql_field( + :workspaces_agent_config, + all_graphql_fields_for("workspaces_agent_configs".classify, max_depth: 1) + ) + ] + ) + end + + let(:query) { graphql_query_for(:project, args, fields) } + + subject(:actual_workspaces_agent_config) do + graphql_dig_at(graphql_data, :project, :clusterAgent, :workspacesAgentConfig) + end +end + +RSpec.shared_examples 'single workspacesAgentConfig query' do + include_context 'in licensed environment' + + context 'when user is authorized' do + include_context 'with authorized user as current user' + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns single workspaces_agent_config' + + context 'when the user requests a workspaces_agent_config that they are not authorized for' do + let(:agent) { unauthorized_workspaces_agent_config.agent } + let(:project) { agent.project } + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns blank' + end + end + + context 'when user is not authorized' do + include_context 'with unauthorized user as current user' + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns blank' + end + + it_behaves_like 'query in unlicensed environment' +end + +RSpec.shared_examples 'query returns single workspaces_agent_config' do + include GraphqlHelpers + + before do + post_graphql(query, current_user: current_user) + end + + it { expect(actual_workspaces_agent_config.fetch('id')).to eq(workspaces_agent_config.to_gid.to_s) } +end diff --git a/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/with_no_args_spec.rb b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/with_no_args_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..214e929168a938f721d9d8e0a494f028de439a53 --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/with_no_args_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative './shared' + +# NOTE: Even though this `single workspacesAgentConfig` spec only has no fields to test, we still use similar +# shared examples patterns and structure as the other multi-model query specs, for consistency. + +RSpec.describe 'Query.project.clusterAgent.workspacesAgentConfig', feature_category: :workspaces do + include_context 'for a Query.project.clusterAgent.workspacesAgentConfig query' + + it_behaves_like 'single workspacesAgentConfig query' +end diff --git a/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/shared.rb b/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/shared.rb index a57a35e4d8ae2f3e1df09107ebe0644c764263e3..ea53ac8916900c4c21c72c05913e2aa6a7f64f90 100644 --- a/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/shared.rb +++ b/ee/spec/requests/api/graphql/remote_development/current_user/workspaces/shared.rb @@ -25,5 +25,5 @@ ) end - subject { graphql_data.dig('currentUser', 'workspaces', 'nodes') } + subject(:actual_workspaces) { graphql_dig_at(graphql_data, :currentUser, :workspaces, :nodes) } end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/TODO_DEPRECATED.md b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/TODO_DEPRECATED.md new file mode 100644 index 0000000000000000000000000000000000000000..9e4a99f59ce95700a017c98bd321d4062d5814ae --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/TODO_DEPRECATED.md @@ -0,0 +1,6 @@ +TODO: namespace.remoteDevelopmentClusterAgents GraphQL is deprecated. + +Remove this entire folder `ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents` +and its contents in 17.10 + +https://gitlab.com/gitlab-org/gitlab/-/issues/480769 diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/remote_development_agent_config_spec.rb b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/remote_development_agent_config_spec.rb deleted file mode 100644 index c0fecd1883f2ef0edff49b692af581f9e0a921e1..0000000000000000000000000000000000000000 --- a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/remote_development_agent_config_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require_relative './shared' - -# TODO: clusterAgent.remoteDevelopmentAgentConfig GraphQL is deprecated - remove in 17.10 - https://gitlab.com/gitlab-org/gitlab/-/issues/480769 -RSpec.describe 'Query.namespace.remote_development_cluster_agents(filter: AVAILABLE) for deprecated remote_development_agent_config field', feature_category: :workspaces do - include GraphqlHelpers - include StubFeatureFlags - - let_it_be(:user) { create(:user) } - let(:agent_config_id) { subject['id'] } - let_it_be(:current_user) { user } - let_it_be(:available_agent) do - create(:ee_cluster_agent, :in_group, :with_existing_workspaces_agent_config).tap do |agent| - agent.project.namespace.add_maintainer(user) - end - end - - let_it_be(:agent_config) { available_agent.remote_development_agent_config } - let_it_be(:namespace) { available_agent.project.namespace } - let_it_be(:namespace_agent_mapping) do - create( - :remote_development_namespace_cluster_agent_mapping, - user: user, - agent: available_agent, - namespace: namespace - ) - end - - let(:fields) do - <<~QUERY - nodes { - remoteDevelopmentAgentConfig { - #{all_graphql_fields_for('remote_development_agent_config'.classify, max_depth: 1)} - } - } - QUERY - end - - let(:query) do - graphql_query_for( - :namespace, - { full_path: namespace.full_path }, - query_graphql_field( - :remote_development_cluster_agents, - { filter: :AVAILABLE }, - fields - ) - ) - end - - subject do - graphql_data.dig('namespace', 'remoteDevelopmentClusterAgents', 'nodes', 0, 'remoteDevelopmentAgentConfig') - end - - before do - stub_licensed_features(remote_development: true) - end - - context 'when the params are valid' do - let(:expected_agent_config_id) do - "gid://gitlab/RemoteDevelopment::RemoteDevelopmentAgentConfig/" \ - "#{agent_config.id}" - end - - let(:expected_agent_config) do - { - 'id' => expected_agent_config_id, - 'projectId' => agent_config.project_id, - 'enabled' => agent_config.enabled, - 'dnsZone' => agent_config.dns_zone, - 'networkPolicyEnabled' => agent_config.network_policy_enabled, - 'gitlabWorkspacesProxyNamespace' => agent_config.gitlab_workspaces_proxy_namespace, - 'workspacesQuota' => agent_config.workspaces_quota, - 'workspacesPerUserQuota' => agent_config.workspaces_per_user_quota, - # noinspection RubyResolve -- https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-32301 - 'defaultMaxHoursBeforeTermination' => agent_config.default_maxHours_before_termination, - 'maxHoursBeforeTerminationLimit' => agent_config.max_hours_before_termination_limit, - 'createdAt' => agent_config.created_at, - 'updatedAt' => agent_config.updated_at - } - end - - it 'returns cluster agents that are available for remote development in the namespace' do - get_graphql(query, current_user: current_user) - - expect(agent_config_id).to eq(expected_agent_config_id) - end - end - - include_examples "checks for remote_development licensed feature" -end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/shared.rb b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/shared.rb index ce2105928dd2d60bb7d47b1a3511ac958d823bbb..fa6220b7ba78478ec4e612871d535853aa3c7e8f 100644 --- a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/shared.rb +++ b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/shared.rb @@ -1,17 +1,22 @@ # frozen_string_literal: true -require_relative '../../shared' +require_relative "../workspaces_cluster_agents/shared" -RSpec.shared_examples 'checks for remote_development licensed feature' do - context 'when remote_development feature is unlicensed' do - before do - stub_licensed_features(remote_development: false) - end +RSpec.shared_context "for a Query.namespace.remote_development_cluster_agents query" do + include_context "for a Query.namespace.workspaces_cluster_agents query" - it 'returns an error' do - post_graphql(query, current_user: current_user) - - expect_graphql_errors_to_include "'remote_development' licensed feature is not available" - end + let(:fields) do + query_graphql_field( + :remote_development_cluster_agents, + attributes, + [ + query_graphql_field( + :nodes, + all_graphql_fields_for("cluster_agents".classify, max_depth: 1) + ) + ] + ) end + + subject(:actual_agents) { graphql_dig_at(graphql_data, :namespace, :remoteDevelopmentClusterAgents, :nodes) } end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_available_filter_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_available_filter_arg_spec.rb index 5070cc13212628ca7bfc72a433dff3674d5bdd5c..6915a0c645c1f8d154068a9ff8a514a387b5a218 100644 --- a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_available_filter_arg_spec.rb +++ b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_available_filter_arg_spec.rb @@ -1,94 +1,19 @@ # frozen_string_literal: true -require 'spec_helper' -require_relative './shared' - -RSpec.describe 'Query.namespace.remote_development_cluster_agents(filter: AVAILABLE)', - feature_category: :workspaces do - include GraphqlHelpers - include StubFeatureFlags - - let_it_be(:user) { create(:user) } - let_it_be(:current_user) { user } - # Setup cluster and user such that the user has the bare minimum permissions - # to be able to receive the agent when calling the API i.e. the user has Developer access - # to the agent project ONLY (and not a group-level access) - let_it_be(:agent) do - create(:ee_cluster_agent, :in_group, :with_existing_workspaces_agent_config).tap do |agent| - agent.project.add_developer(user) - end - end - - let_it_be(:namespace) { agent.project.namespace } - let_it_be(:namespace_agent_mapping) do - create( - :remote_development_namespace_cluster_agent_mapping, - user: user, - agent: agent, - namespace: namespace - ) - end - - let(:fields) do - <<~QUERY - nodes { - #{all_graphql_fields_for('cluster_agents'.classify, max_depth: 1)} - } - QUERY - end - - let(:agent_names_in_response) { subject.pluck('name') } - let(:query) do - graphql_query_for( - :namespace, - { full_path: namespace.full_path }, - query_graphql_field( - :remote_development_cluster_agents, - { filter: :AVAILABLE }, - fields - ) - ) - end - - subject { graphql_data.dig('namespace', 'remoteDevelopmentClusterAgents', 'nodes') } - - before do - stub_licensed_features(remote_development: true) - end - - context 'when the params are valid' do - it 'returns a cluster agent' do - post_graphql(query, current_user: current_user) - - expect(agent_names_in_response).to eq([agent.name]) - end - end - - include_examples "checks for remote_development licensed feature" - - context 'when the provided namespace is not a group namespace' do - let(:namespace) { agent.project.project_namespace } - - it 'returns an error' do - post_graphql(query, current_user: current_user) - - expect_graphql_errors_to_include "does not exist or you don't have permission to perform this action" - end - end - - context 'when user does not have access to the project' do - # simulate test conditions by creating the maximum privileged user that does/should - # not have the permission to access the agent - let(:current_user) do - create(:user).tap do |user| - agent.project.add_reporter(user) - end - end - - it 'skips agents for which the user does not have access' do - post_graphql(query, current_user: current_user) - - expect(agent_names_in_response).to eq([]) - end - end +require "spec_helper" +require_relative "./shared" + +RSpec.describe "Query.namespace.remote_development_cluster_agents(filter: AVAILABLE)", feature_category: :workspaces do + let(:filter) { :AVAILABLE } + let(:agent) { available_agent } + let(:expected_agents) { [available_agent] } + let_it_be(:authorized_user_project_access_level) { :developer } + let_it_be(:authorized_user_namespace_access_level) { nil } + let_it_be(:unauthorized_user_project_access_level) { :reporter } + let_it_be(:unauthorized_user_namespace_access_level) { nil } + + include_context "with filter argument" + include_context "for a Query.namespace.remote_development_cluster_agents query" + + it_behaves_like "multiple agents query" end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_directly_mapped_filter_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_directly_mapped_filter_arg_spec.rb index 0d3d602a3732f532133dab610d3eee5ab7c1edcd..3296e20f3cd3bf3e005d2e6f497b254fea14480c 100644 --- a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_directly_mapped_filter_arg_spec.rb +++ b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_directly_mapped_filter_arg_spec.rb @@ -1,98 +1,19 @@ # frozen_string_literal: true -require 'spec_helper' -require_relative './shared' - -RSpec.describe 'Query.namespace.remote_development_cluster_agents(filter: DIRECTLY_MAPPED)', - feature_category: :workspaces do - include GraphqlHelpers - include StubFeatureFlags - - let_it_be(:user) { create(:user) } - let_it_be(:current_user) { user } - # Setup cluster and user such that the user has the bare minimum permissions - # to be able to retrieve directly mapped agent when calling the API i.e. - # the user has Maintainer access for the group - let_it_be(:mapped_agent) do - create(:ee_cluster_agent, :in_group, :with_existing_workspaces_agent_config).tap do |agent| - agent.project.namespace.add_maintainer(user) - end - end - - let_it_be(:unmapped_agent) do - create(:ee_cluster_agent, :with_existing_workspaces_agent_config, project: mapped_agent.project) - end - - let_it_be(:namespace) { mapped_agent.project.namespace } - let_it_be(:namespace_agent_mapping) do - create( - :remote_development_namespace_cluster_agent_mapping, - user: user, - agent: mapped_agent, - namespace: namespace - ) - end - - let(:fields) do - <<~QUERY - nodes { - #{all_graphql_fields_for('cluster_agents'.classify, max_depth: 1)} - } - QUERY - end - - let(:agent_names_in_response) { subject.pluck('name') } - let(:query) do - graphql_query_for( - :namespace, - { full_path: namespace.full_path }, - query_graphql_field( - :remote_development_cluster_agents, - { filter: :DIRECTLY_MAPPED }, - fields - ) - ) - end - - subject { graphql_data.dig('namespace', 'remoteDevelopmentClusterAgents', 'nodes') } - - before do - stub_licensed_features(remote_development: true) - end - - context 'when the params are valid' do - it 'returns cluster agents that are directly mapped to the namespace' do - post_graphql(query, current_user: current_user) - - expect(agent_names_in_response).to eq([mapped_agent.name]) - end - end - - context 'when the passed namespace is not a group' do - let(:namespace) { mapped_agent.project.project_namespace } - - it 'returns an error' do - post_graphql(query, current_user: current_user) - - expect_graphql_errors_to_include "does not exist or you don't have permission to perform this action" - end - end - - include_examples "checks for remote_development licensed feature" - - context 'when user does not have access to view the mappings' do - # simulate test conditions by creating the maximum privileged user that does/should - # not have the permission to access the agent - let(:current_user) do - create(:user).tap do |user| - namespace.add_developer(user) - end - end - - it 'returns an error' do - post_graphql(query, current_user: current_user) - - expect_graphql_errors_to_include "does not exist or you don't have permission to perform this action" - end - end +require "spec_helper" +require_relative "./shared" + +RSpec.describe "Query.namespace.remote_development_cluster_agents(filter: DIRECTLY_MAPPED)", feature_category: :workspaces do + let(:filter) { :DIRECTLY_MAPPED } + let(:agent) { directly_mapped_agent } + let(:expected_agents) { [directly_mapped_agent, available_agent] } + let_it_be(:authorized_user_project_access_level) { nil } + let_it_be(:authorized_user_namespace_access_level) { :maintainer } + let_it_be(:unauthorized_user_project_access_level) { nil } + let_it_be(:unauthorized_user_namespace_access_level) { :developer } + + include_context "with filter argument" + include_context "for a Query.namespace.remote_development_cluster_agents query" + + it_behaves_like "multiple agents query" end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_unmapped_filter_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_unmapped_filter_arg_spec.rb index 6f8e8bd6e190c0cd182f21a532ce859835821768..16cee30710f391be28e2e7414a35d88c1b11503c 100644 --- a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_unmapped_filter_arg_spec.rb +++ b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_unmapped_filter_arg_spec.rb @@ -3,93 +3,17 @@ require 'spec_helper' require_relative './shared' -RSpec.describe 'Query.namespace.remote_development_cluster_agents(filter: UNMAPPED)', - feature_category: :workspaces do - include GraphqlHelpers - include StubFeatureFlags - - let_it_be(:user) { create(:user) } - let_it_be(:current_user) { user } - let_it_be(:mapped_agent) do - create(:ee_cluster_agent, :in_group, :with_existing_workspaces_agent_config).tap do |agent| - agent.project.namespace.add_maintainer(user) - end - end - - let_it_be(:unmapped_agent) do - create(:ee_cluster_agent, :with_existing_workspaces_agent_config, project: mapped_agent.project) - end - - let_it_be(:namespace) { mapped_agent.project.namespace } - let_it_be(:namespace_agent_mapping) do - create( - :remote_development_namespace_cluster_agent_mapping, - user: user, - agent: mapped_agent, - namespace: namespace - ) - end - - let(:fields) do - <<~QUERY - nodes { - #{all_graphql_fields_for('cluster_agents'.classify, max_depth: 1)} - } - QUERY - end - - let(:agent_names_in_response) { subject.pluck('name') } - let(:query) do - graphql_query_for( - :namespace, - { full_path: namespace.full_path }, - query_graphql_field( - :remote_development_cluster_agents, - { filter: :UNMAPPED }, - fields - ) - ) - end - - subject { graphql_data.dig('namespace', 'remoteDevelopmentClusterAgents', 'nodes') } - - before do - stub_licensed_features(remote_development: true) - end - - context 'when the params are valid' do - it 'returns cluster agents that are not mapped to the namespace' do - post_graphql(query, current_user: current_user) - - expect(agent_names_in_response).to eq([unmapped_agent.name]) - end - end - - include_examples "checks for remote_development licensed feature" - - context 'when the passed namespace is not a group' do - let(:namespace) { mapped_agent.project.project_namespace } - - it 'returns an error' do - post_graphql(query, current_user: current_user) - - expect_graphql_errors_to_include "does not exist or you don't have permission to perform this action" - end - end - - context 'when user does not have access to view the mappings' do - # simulate test conditions by creating the maximum privileged user that does/should - # not have the permission to access the agent - let(:current_user) do - create(:user).tap do |user| - namespace.add_developer(user) - end - end - - it 'returns an error' do - post_graphql(query, current_user: current_user) - - expect_graphql_errors_to_include "does not exist or you don't have permission to perform this action" - end - end +RSpec.describe 'Query.namespace.remote_development_cluster_agents(filter: UNMAPPED)', feature_category: :workspaces do + let(:filter) { :UNMAPPED } + let(:agent) { unmapped_agent } + let(:expected_agents) { [unmapped_agent] } + let_it_be(:authorized_user_project_access_level) { nil } + let_it_be(:authorized_user_namespace_access_level) { :maintainer } + let_it_be(:unauthorized_user_project_access_level) { nil } + let_it_be(:unauthorized_user_namespace_access_level) { :developer } + + include_context "with filter argument" + include_context "for a Query.namespace.remote_development_cluster_agents query" + + it_behaves_like "multiple agents query" end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/workspaces_agent_config_spec.rb b/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/workspaces_agent_config_spec.rb deleted file mode 100644 index 7cb936b6fe3345966c88b3e5d175de23261d2f7c..0000000000000000000000000000000000000000 --- a/ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/workspaces_agent_config_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require_relative './shared' - -RSpec.describe 'Query.namespace.remote_development_cluster_agents(filter: AVAILABLE)', feature_category: :workspaces do - include GraphqlHelpers - include StubFeatureFlags - - let_it_be(:user) { create(:user) } - let(:agent_config_id) { subject['id'] } - let_it_be(:current_user) { user } - let_it_be(:available_agent) do - create(:ee_cluster_agent, :in_group, :with_existing_workspaces_agent_config).tap do |agent| - agent.project.namespace.add_maintainer(user) - end - end - - let_it_be(:agent_config) { available_agent.unversioned_latest_workspaces_agent_config } - let_it_be(:namespace) { available_agent.project.namespace } - let_it_be(:namespace_agent_mapping) do - create( - :remote_development_namespace_cluster_agent_mapping, - user: user, - agent: available_agent, - namespace: namespace - ) - end - - let(:fields) do - <<~QUERY - nodes { - workspacesAgentConfig { - #{all_graphql_fields_for('workspaces_agent_config'.classify, max_depth: 1)} - } - } - QUERY - end - - let(:query) do - graphql_query_for( - :namespace, - { full_path: namespace.full_path }, - query_graphql_field( - :remote_development_cluster_agents, - { filter: :AVAILABLE }, - fields - ) - ) - end - - subject do - graphql_data.dig('namespace', 'remoteDevelopmentClusterAgents', 'nodes', 0, 'workspacesAgentConfig') - end - - before do - stub_licensed_features(remote_development: true) - end - - context 'when the params are valid' do - let(:expected_agent_config_id) do - "gid://gitlab/RemoteDevelopment::WorkspacesAgentConfig/" \ - "#{agent_config.id}" - end - - let(:expected_agent_config) do - { - 'id' => expected_agent_config_id, - 'projectId' => agent_config.project_id, - 'enabled' => agent_config.enabled, - 'dnsZone' => agent_config.dns_zone, - 'networkPolicyEnabled' => agent_config.network_policy_enabled, - 'gitlabWorkspacesProxyNamespace' => agent_config.gitlab_workspaces_proxy_namespace, - 'workspacesQuota' => agent_config.workspaces_quota, - 'workspacesPerUserQuota' => agent_config.workspaces_per_user_quota, - # noinspection RubyResolve -- https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-32301 - 'defaultMaxHoursBeforeTermination' => agent_config.default_maxHours_before_termination, - 'maxHoursBeforeTerminationLimit' => agent_config.max_hours_before_termination_limit, - 'createdAt' => agent_config.created_at, - 'updatedAt' => agent_config.updated_at - } - end - - it 'returns cluster agents that are available for remote development in the namespace' do - get_graphql(query, current_user: current_user) - - expect(agent_config_id).to eq(expected_agent_config_id) - end - end - - include_examples "checks for remote_development licensed feature" -end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/shared.rb b/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/shared.rb new file mode 100644 index 0000000000000000000000000000000000000000..ffd020990191ee32cd9a1cd3be089f7d3d02ef0d --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/shared.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +require_relative "../../shared" + +#------------------------------------------------- +# SHARED CONTEXTS - INDIVIDUAL ARGUMENTS SCENARIOS +#------------------------------------------------- + +# noinspection RubyArgCount +RSpec.shared_context "with filter argument" do + let_it_be(:namespace) { create(:group, name: "group-namespace") } + let_it_be(:project) do + create(:project, :in_group, path: "project", namespace: namespace) + end + + let_it_be(:authorized_user) do + create(:user).tap do |user| + # create the minimum privileged user that should have the project and namespace + # permissions to access the agent. + project.add_member(user, authorized_user_project_access_level) if authorized_user_project_access_level + namespace.add_member(user, authorized_user_namespace_access_level) if authorized_user_namespace_access_level + end + end + + let_it_be(:unauthorized_user) do + # create the maximum privileged user that should NOT have the project and namespace + # permissions to access the agent. + create(:user).tap do |user| + project.add_member(user, unauthorized_user_project_access_level) if unauthorized_user_project_access_level + namespace.add_member(user, unauthorized_user_namespace_access_level) if unauthorized_user_namespace_access_level + end + end + + let_it_be(:project_namespace) { project.project_namespace } + + let_it_be(:available_agent) do + create(:ee_cluster_agent, :with_existing_workspaces_agent_config, project: project, + created_by_user: authorized_user, name: "available-agent").tap do |agent| + create( + :remote_development_namespace_cluster_agent_mapping, + user: authorized_user, + agent: agent, + namespace: namespace + ) + end + end + + let_it_be(:directly_mapped_agent) do + create(:cluster_agent, project: project, created_by_user: authorized_user, + name: "directly-mapped-agent").tap do |agent| + create( + :remote_development_namespace_cluster_agent_mapping, + user: authorized_user, + agent: agent, + namespace: namespace + ) + end + end + + let_it_be(:unmapped_agent) do + create(:cluster_agent, project: project, created_by_user: authorized_user, name: "unmapped-agent") + end + + let_it_be(:unauthorized_agent) { create(:cluster_agent, :in_group) } +end + +RSpec.shared_context "for a Query.namespace.workspaces_cluster_agents query" do + include GraphqlHelpers + + let(:args) { { full_path: namespace.full_path } } + + let(:attributes) { { filter: filter } } + + let(:fields) do + query_graphql_field( + :workspaces_cluster_agents, + attributes, + [ + query_graphql_field( + :nodes, + all_graphql_fields_for("cluster_agents".classify, max_depth: 1) + ) + ] + ) + end + + let(:query) { graphql_query_for(:namespace, args, fields) } + + subject(:actual_agents) { graphql_dig_at(graphql_data, :namespace, :workspacesClusterAgents, :nodes) } +end + +#------------------------------------------------ +# SHARED EXAMPLES - MAIN ENTRY POINTS FOR TESTING +#------------------------------------------------ + +RSpec.shared_examples "multiple agents query" do + include_context "in licensed environment" + + let(:agent_names) { actual_agents.pluck("name") } + let(:expected_agent_names) { expected_agents.pluck("name").sort } + + context "when user is authorized" do + include_context "with authorized user as current user" + + it_behaves_like "query is a working graphql query" + + context "when the user requests an agent that they are authorized for" do + before do + post_graphql(query, current_user: current_user) + end + + it "includes only the expected agent", :unlimited_max_formatted_output_length do + expect(agent_names.sort).to eq(expected_agent_names) + end + end + + context "when the user requests an agent that they are not authorized for" do + before do + post_graphql(query, current_user: current_user) + end + + it "does not return the unauthorized agent" do + expect(agent_names).not_to include(unauthorized_agent.name) + end + + it "still returns the authorized agent" do + expect(agent_names).to include(agent.name) + end + end + + context "when the provided namespace is not a group namespace" do + let(:namespace) { project_namespace } + + it_behaves_like "query returns blank" + it_behaves_like "query includes graphql error", + "does not exist or you don't have permission to perform this action" + end + end + + context "when user is not authorized" do + include_context "with unauthorized user as current user" + + it_behaves_like "query is a working graphql query" + it_behaves_like "query returns blank" + end + + it_behaves_like "query in unlicensed environment" +end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_available_filter_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_available_filter_arg_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..ac2c7b256f8fdec7a71fd9a5fe8bd46e2fa455e1 --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_available_filter_arg_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "spec_helper" +require_relative "./shared" + +RSpec.describe "Query.namespace.remote_development_cluster_agents(filter: AVAILABLE)", feature_category: :workspaces do + let(:filter) { :AVAILABLE } + let(:agent) { available_agent } + let(:expected_agents) { [available_agent] } + let_it_be(:authorized_user_project_access_level) { :developer } + let_it_be(:authorized_user_namespace_access_level) { nil } + let_it_be(:unauthorized_user_project_access_level) { :reporter } + let_it_be(:unauthorized_user_namespace_access_level) { nil } + + include_context "with filter argument" + include_context "for a Query.namespace.workspaces_cluster_agents query" + + it_behaves_like "multiple agents query" +end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_directly_mapped_filter_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_directly_mapped_filter_arg_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..c0db7505acc9d9e6aa9b2dde8063f57a27a8e52a --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_directly_mapped_filter_arg_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "spec_helper" +require_relative "./shared" + +RSpec.describe "Query.namespace.remote_development_cluster_agents(filter: DIRECTLY_MAPPED)", feature_category: :workspaces do + let(:filter) { :DIRECTLY_MAPPED } + let(:agent) { directly_mapped_agent } + let(:expected_agents) { [directly_mapped_agent, available_agent] } + let_it_be(:authorized_user_project_access_level) { nil } + let_it_be(:authorized_user_namespace_access_level) { :maintainer } + let_it_be(:unauthorized_user_project_access_level) { nil } + let_it_be(:unauthorized_user_namespace_access_level) { :developer } + + include_context "with filter argument" + include_context "for a Query.namespace.workspaces_cluster_agents query" + + it_behaves_like "multiple agents query" +end diff --git a/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_unmapped_filter_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_unmapped_filter_arg_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9246ca1598448bef98f5a7f83f05feb9ce20dc4c --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_unmapped_filter_arg_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative './shared' + +RSpec.describe 'Query.namespace.remote_development_cluster_agents(filter: UNMAPPED)', feature_category: :workspaces do + let(:filter) { :UNMAPPED } + let(:agent) { unmapped_agent } + let(:expected_agents) { [unmapped_agent] } + let_it_be(:authorized_user_project_access_level) { nil } + let_it_be(:authorized_user_namespace_access_level) { :maintainer } + let_it_be(:unauthorized_user_project_access_level) { nil } + let_it_be(:unauthorized_user_namespace_access_level) { :developer } + + include_context "with filter argument" + include_context "for a Query.namespace.workspaces_cluster_agents query" + + it_behaves_like "multiple agents query" +end diff --git a/ee/spec/requests/api/graphql/remote_development/shared.rb b/ee/spec/requests/api/graphql/remote_development/shared.rb index ce4c26aa9e214ee6c9c11e7529b455c352fc3922..f3ad366f35fa76332b66027cf10dbb65ea7ae083 100644 --- a/ee/spec/requests/api/graphql/remote_development/shared.rb +++ b/ee/spec/requests/api/graphql/remote_development/shared.rb @@ -16,20 +16,6 @@ let_it_be(:args) { {} } end -RSpec.shared_context 'with id arg' do - include_context 'with unauthorized workspace created' - - let_it_be(:workspace, reload: true) { create(:workspace, name: 'matching-workspace') } - - # create workspace with different ID but still owned by the same user, to ensure isn't returned by the query - let_it_be(:non_matching_workspace, reload: true) do - create(:workspace, user: workspace.user, name: 'non-matching-workspace') - end - - let(:id) { workspace.to_global_id.to_s } - let(:args) { { id: id } } -end - RSpec.shared_context 'with ids argument' do include_context 'with unauthorized workspace created' @@ -97,35 +83,6 @@ # SHARED EXAMPLES - MAIN ENTRY POINTS FOR TESTING #------------------------------------------------ -RSpec.shared_examples 'single workspace query' do |authorized_user_is_admin: false| - include_context 'in licensed environment' - - context 'when user is authorized' do - include_context 'with authorized user as current user' - - it_behaves_like 'query is a working graphql query' - it_behaves_like 'query returns single workspace' - - unless authorized_user_is_admin - context 'when the user requests a workspace that they are not authorized for' do - let(:id) { unauthorized_workspace.to_global_id.to_s } - - it_behaves_like 'query is a working graphql query' - it_behaves_like 'query returns blank' - end - end - end - - context 'when user is not authorized' do - include_context 'with unauthorized user as current user' - - it_behaves_like 'query is a working graphql query' - it_behaves_like 'query returns blank' - end - - it_behaves_like 'query in unlicensed environment' -end - RSpec.shared_examples 'multiple workspaces query' do |authorized_user_is_admin: false, expected_error_regex: nil| include_context 'in licensed environment' @@ -243,6 +200,8 @@ end RSpec.shared_examples 'query returns single workspace' do + include GraphqlHelpers + before do post_graphql(query, current_user: current_user) end diff --git a/ee/spec/requests/api/graphql/remote_development/workspace/shared.rb b/ee/spec/requests/api/graphql/remote_development/workspace/shared.rb new file mode 100644 index 0000000000000000000000000000000000000000..bf7b532ebd1f14aad516e1f17ef76f980e20c1f9 --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/workspace/shared.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require_relative '../shared' + +RSpec.shared_context 'with id arg' do + include_context 'with unauthorized workspace created' + + let_it_be(:workspace, reload: true) { create(:workspace, name: 'matching-workspace') } + + # create workspace with different ID but still owned by the same user, to ensure isn't returned by the query + let_it_be(:non_matching_workspace, reload: true) do + create(:workspace, user: workspace.user, name: 'non-matching-workspace') + end + + let(:id) { workspace.to_global_id.to_s } + let(:args) { { id: id } } +end + +RSpec.shared_context 'for a Query.workspace query' do + include GraphqlHelpers + + include_context "with authorized user as developer on workspace's project" + + let(:fields) do + <<~QUERY + #{all_graphql_fields_for('workspace'.classify, max_depth: 1)} + QUERY + end + + let(:query) { graphql_query_for('workspace', args, fields) } + + subject(:actual_workspace) { graphql_data['workspace'] } +end + +RSpec.shared_examples 'single workspace query' do |authorized_user_is_admin: false| + include_context 'in licensed environment' + + context 'when user is authorized' do + include_context 'with authorized user as current user' + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns single workspace' + + unless authorized_user_is_admin + context 'when the user requests a workspace that they are not authorized for' do + let(:id) { unauthorized_workspace.to_global_id.to_s } + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns blank' + end + end + end + + context 'when user is not authorized' do + include_context 'with unauthorized user as current user' + + it_behaves_like 'query is a working graphql query' + it_behaves_like 'query returns blank' + end + + it_behaves_like 'query in unlicensed environment' +end diff --git a/ee/spec/requests/api/graphql/remote_development/workspace/with_id_arg_spec.rb b/ee/spec/requests/api/graphql/remote_development/workspace/with_id_arg_spec.rb index 29aa4a19ec45dd9b6e34a2b6d73798b2de08d8e5..dbf8f4a4f3a3aa30b68c929b1b1c9d24a25707e1 100644 --- a/ee/spec/requests/api/graphql/remote_development/workspace/with_id_arg_spec.rb +++ b/ee/spec/requests/api/graphql/remote_development/workspace/with_id_arg_spec.rb @@ -1,28 +1,12 @@ # frozen_string_literal: true require 'spec_helper' -require_relative '../shared' +require_relative './shared' -RSpec.describe 'Query.workspace(id: RemoteDevelopmentWorkspaceID!)', feature_category: :workspaces do - include GraphqlHelpers - - # NOTE: Even though this single-workspace spec only has one field scenario to test, we still use the same - # shared examples patterns and structure as the other multi-workspace query specs, for consistency. - - RSpec.shared_context 'for a Query.workspace query' do - include_context "with authorized user as developer on workspace's project" - - let(:fields) do - <<~QUERY - #{all_graphql_fields_for('workspace'.classify, max_depth: 1)} - QUERY - end - - let(:query) { graphql_query_for('workspace', args, fields) } - - subject { graphql_data['workspace'] } - end +# NOTE: Even though this single-workspace spec only has one field scenario to test, we still use similar +# shared examples patterns and structure as the other multi-workspace query specs, for consistency. +RSpec.describe 'Query.workspace(id: RemoteDevelopmentWorkspaceID!)', feature_category: :workspaces do include_context 'with id arg' include_context 'for a Query.workspace query' diff --git a/ee/spec/requests/api/graphql/remote_development/workspaces/shared.rb b/ee/spec/requests/api/graphql/remote_development/workspaces/shared.rb index 57500dca65b137204f5ec7e1ad26afc8c8d5b0f4..dde1f92233e1e6da087faf6b1a8aa4a63f4ee3a7 100644 --- a/ee/spec/requests/api/graphql/remote_development/workspaces/shared.rb +++ b/ee/spec/requests/api/graphql/remote_development/workspaces/shared.rb @@ -21,7 +21,7 @@ let(:query) { graphql_query_for('workspaces', args, fields) } - subject { graphql_data.dig('workspaces', 'nodes') } + subject(:actual_workspaces) { graphql_dig_at(graphql_data, :workspaces, :nodes) } before do workspace.project.add_developer(workspace_owner) diff --git a/scripts/verify-tff-mapping b/scripts/verify-tff-mapping index 561cea8f7f8e43bef9ca88d0179794e7aeb71bd0..f2c43baa3235bb43434edde7a87e4ef4e07258af 100755 --- a/scripts/verify-tff-mapping +++ b/scripts/verify-tff-mapping @@ -436,6 +436,25 @@ tests = [ changed_file: 'ee/app/graphql/mutations/boards/epic_lists/destroy.rb', expected: ['ee/spec/requests/api/graphql/mutations/boards/epic_lists/destroy_spec.rb'] }, + ## BEGIN Remote development GraphQL mutations + { + explanation: 'Map Remote Development GraphQL mutations to request specs', + changed_file: 'ee/app/graphql/mutations/remote_development/namespace_cluster_agent_mapping_operations/create.rb', + # rubocop:disable Layout/LineLength -- fix CI failures - not sure why other lines in this file don't get errors + expected: %w[ + ee/spec/requests/api/graphql/mutations/remote_development/namespace_cluster_agent_mapping_operations/create_spec.rb + ] + # rubocop:enable Layout/LineLength + }, + { + explanation: 'Map Remote Development GraphQL mutations to request specs', + changed_file: 'ee/app/graphql/mutations/remote_development/namespace_cluster_agent_mapping_operations/delete.rb', + # rubocop:disable Layout/LineLength -- fix CI failures - not sure why other lines in this file don't get errors + expected: %w[ + ee/spec/requests/api/graphql/mutations/remote_development/namespace_cluster_agent_mapping_operations/delete_spec.rb + ] + # rubocop:enable Layout/LineLength + }, { explanation: 'Map Remote Development GraphQL mutations to request specs', changed_file: 'ee/app/graphql/mutations/remote_development/workspace_operations/create.rb', @@ -444,8 +463,40 @@ tests = [ ] }, { - explanation: 'Map Remote Development GraphQL cluster_agent workspaces resolvers to request specs', - changed_file: 'ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb', + explanation: 'Map Remote Development GraphQL mutations to request specs', + changed_file: 'ee/app/graphql/mutations/remote_development/workspace_operations/update.rb', + expected: %w[ + ee/spec/requests/api/graphql/mutations/remote_development/workspace_operations/update_spec.rb + ] + }, + ## END Remote development GraphQL mutations + + ## BEGIN Remote development GraphQL resolvers (in alphabetical order by resolver source file path) + { + explanation: 'Map Remote Development GraphQL query root admin_workspaces_resolver.rb to request specs', + changed_file: 'ee/app/graphql/resolvers/remote_development/admin_workspaces_resolver.rb', + expected: %w[ + ee/spec/requests/api/graphql/remote_development/workspace/with_id_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/workspaces/with_actual_states_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/workspaces/with_agent_ids_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/workspaces/with_ids_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/workspaces/with_no_args_spec.rb + ee/spec/requests/api/graphql/remote_development/workspaces/with_project_ids_arg_spec.rb + ] + }, + { + explanation: + 'Map Remote Development GraphQL cluster_agent/remote_development_agent_config_resolver.rb to request specs', + changed_file: + 'ee/app/graphql/resolvers/remote_development/cluster_agent/remote_development_agent_config_resolver.rb', + expected: %w[ + ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/with_no_args_spec.rb + ] + }, + { + explanation: + 'Map Remote Development GraphQL cluster_agent/workspaces_resolver.rb to request specs', + changed_file: 'ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_resolver.rb', expected: %w[ ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_actual_states_arg_spec.rb ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/with_ids_arg_spec.rb @@ -454,9 +505,32 @@ tests = [ ] }, { - explanation: 'Map Remote Development GraphQL current_user workspaces resolvers to request specs', - changed_file: 'ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver.rb', + explanation: + 'Map Remote Development GraphQL cluster_agent/workspaces_agent_config_resolver.rb to request specs', + changed_file: 'ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_agent_config_resolver.rb', + expected: %w[ + ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/with_no_args_spec.rb + ] + }, + { + explanation: 'Map Remote Development GraphQL namespace/cluster_agents_resolver.rb to request specs', + changed_file: 'ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb', + # rubocop:disable Layout/LineLength -- fix CI failures - not sure why other lines in this file don't get errors + expected: %w[ + ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_available_filter_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_directly_mapped_filter_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/with_unmapped_filter_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_available_filter_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_directly_mapped_filter_arg_spec.rb + ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_unmapped_filter_arg_spec.rb + ] + # rubocop:enable Layout/LineLength + }, + { + explanation: 'Map Remote Development GraphQL workspaces_resolver.rb to request specs', + changed_file: 'ee/app/graphql/resolvers/remote_development/workspaces_resolver.rb', expected: %w[ + ee/spec/requests/api/graphql/remote_development/workspace/with_id_arg_spec.rb ee/spec/requests/api/graphql/remote_development/current_user/workspaces/with_actual_states_arg_spec.rb ee/spec/requests/api/graphql/remote_development/current_user/workspaces/with_agent_ids_arg_spec.rb ee/spec/requests/api/graphql/remote_development/current_user/workspaces/with_ids_arg_spec.rb @@ -464,17 +538,7 @@ tests = [ ee/spec/requests/api/graphql/remote_development/current_user/workspaces/with_project_ids_arg_spec.rb ] }, - { - explanation: 'Map Remote Development GraphQL query root workspaces resolvers to request specs', - changed_file: 'ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver.rb', - expected: %w[ - ee/spec/requests/api/graphql/remote_development/workspaces/with_actual_states_arg_spec.rb - ee/spec/requests/api/graphql/remote_development/workspaces/with_agent_ids_arg_spec.rb - ee/spec/requests/api/graphql/remote_development/workspaces/with_ids_arg_spec.rb - ee/spec/requests/api/graphql/remote_development/workspaces/with_no_args_spec.rb - ee/spec/requests/api/graphql/remote_development/workspaces/with_project_ids_arg_spec.rb - ] - }, + { explanation: 'Map Remote Development GraphQL query root workspace type resolver to request specs', changed_file: 'ee/app/graphql/types/remote_development/workspace_type.rb', @@ -483,6 +547,7 @@ tests = [ ee/spec/requests/api/graphql/remote_development/workspace/with_id_arg_spec.rb ] }, + ## END Remote development GraphQL resolvers { explanation: 'https://gitlab.com/gitlab-org/gitlab/-/issues/466068#note_1987834618', diff --git a/tests.yml b/tests.yml index 57dbd2b02f9dbf0d3a41418a811f6bfa9b615caa..826dee564e592a27907ee5bea6d4bc78fe02c716 100644 --- a/tests.yml +++ b/tests.yml @@ -123,18 +123,39 @@ mapping: - source: '(?ee/)?app/mailers/(?ee/)?previews/.+\.rb' test: 'spec/mailers/previews_spec.rb' - ## Remote development GraphQL resolvers - - source: 'ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver\.rb' + ## BEGIN Remote development GraphQL resolvers (in alphabetical order by resolver source file path) + - source: 'ee/app/graphql/resolvers/remote_development/admin_workspaces_resolver\.rb' + test: + - 'ee/spec/requests/api/graphql/remote_development/workspace/*_spec.rb' + - 'ee/spec/requests/api/graphql/remote_development/workspaces/*_spec.rb' + + - source: 'ee/app/graphql/resolvers/remote_development/cluster_agent/remote_development_agent_config_resolver\.rb' + test: + - 'ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/*_spec.rb' + + - source: 'ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_agent_config_resolver\.rb' + test: + - 'ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/*_spec.rb' + + - source: 'ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_resolver\.rb' test: - 'ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces/*_spec.rb' - - source: 'ee/app/graphql/resolvers/remote_development/workspaces_for_current_user_resolver\.rb' + - source: 'ee/app/graphql/resolvers/remote_development/workspaces_resolver\.rb' test: - 'ee/spec/requests/api/graphql/remote_development/current_user/workspaces/*_spec.rb' - - source: 'ee/app/graphql/resolvers/remote_development/workspaces_for_query_root_resolver\.rb' + - source: 'ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver\.rb' test: - - 'ee/spec/requests/api/graphql/remote_development/workspaces/*_spec.rb' + - 'ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/*_spec.rb' + - 'ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/*_spec.rb' + + - source: 'ee/app/graphql/resolvers/remote_development/workspaces_resolver\.rb' + test: + - 'ee/spec/requests/api/graphql/remote_development/current_user/workspaces/*_spec.rb' + - 'ee/spec/requests/api/graphql/remote_development/workspace/*_spec.rb' + + ## END Remote development GraphQL resolvers - source: 'ee/app/graphql/types/remote_development/workspace_type\.rb' test: