From b62f8c36f553f00078bf9da2bd693ff01dc29c08 Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Mon, 9 Dec 2024 10:01:04 +0530 Subject: [PATCH 1/3] Several cleanups related to Workspaces graphql resolvers - Add missing 'Group.workspacesClusterAgents' field, deprecate 'Group.remoteDevelopmentClusterAgents' field - Update README under 'ee/spec/requests/api/graphql/remote_development' to provide more info and guidance - Add missing specs under 'ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config' - Refactor existing specs under ''ee/spec/requests/api/graphql/remote_development' to match existing patterns - Change structure of namespaces under 'ee/app/graphql/resolvers/remote_development' to more closely match structure of corresponding request specs. - Update and add missing entries to 'tests.yml' and 'scripts/verify-tff-mapping' - Change 'ee/app/finders/remote_development/cluster_agents_finder.rb' to return empty array rather than raise error in the case of unauthorized access (to match existing graphql standard patterns). - Other various cleanups See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175486 --- doc/api/graphql/reference/index.md | 40 +++++ .../cluster_agents_finder.rb | 11 +- .../graphql/ee/types/clusters/agent_type.rb | 6 +- ee/app/graphql/ee/types/current_user_type.rb | 2 +- ee/app/graphql/ee/types/namespace_type.rb | 11 +- ee/app/graphql/ee/types/query_type.rb | 2 +- ee/app/graphql/ee/types/user_type.rb | 2 +- ...solver.rb => admin_workspaces_resolver.rb} | 2 +- .../agents_for_namespace_resolver.rb | 31 ---- ...emote_development_agent_config_resolver.rb | 46 ++++++ .../workspaces_agent_config_resolver.rb | 45 ++++++ .../cluster_agent/workspaces_resolver.rb | 45 ++++++ .../namespace/cluster_agents_resolver.rb | 33 ++++ ...lopment_agent_config_for_agent_resolver.rb | 48 ------ ...kspaces_agent_config_for_agent_resolver.rb | 47 ------ .../workspaces_for_agent_resolver.rb | 43 ----- ...ser_resolver.rb => workspaces_resolver.rb} | 2 +- ee/lib/remote_development/README.md | 1 + .../cluster_agents_finder_spec.rb | 8 +- .../graphql/types/current_user_type_spec.rb | 2 +- .../api/graphql/remote_development/README.md | 95 +++++++++-- .../TODO_DEPRECATED.md | 6 + .../remote_development_agent_config/shared.rb | 91 +++++++++++ .../with_no_args_spec.rb | 13 ++ .../cluster_agent/workspaces/shared.rb | 2 +- .../workspaces_agent_config/shared.rb | 88 +++++++++++ .../with_no_args_spec.rb | 13 ++ .../current_user/workspaces/shared.rb | 2 +- .../TODO_DEPRECATED.md | 6 + .../remote_development_agent_config_spec.rb | 93 ----------- .../shared.rb | 27 ++-- .../with_available_filter_arg_spec.rb | 107 ++----------- .../with_directly_mapped_filter_arg_spec.rb | 111 ++----------- .../with_unmapped_filter_arg_spec.rb | 102 ++---------- .../workspaces_agent_config_spec.rb | 92 ----------- .../workspaces_cluster_agents/shared.rb | 148 ++++++++++++++++++ .../with_available_filter_arg_spec.rb | 19 +++ .../with_directly_mapped_filter_arg_spec.rb | 19 +++ .../with_unmapped_filter_arg_spec.rb | 19 +++ .../api/graphql/remote_development/shared.rb | 45 +----- .../remote_development/workspace/shared.rb | 62 ++++++++ .../workspace/with_id_arg_spec.rb | 24 +-- .../remote_development/workspaces/shared.rb | 2 +- scripts/verify-tff-mapping | 95 +++++++++-- tests.yml | 31 +++- 45 files changed, 978 insertions(+), 761 deletions(-) rename ee/app/graphql/resolvers/remote_development/{workspaces_for_query_root_resolver.rb => admin_workspaces_resolver.rb} (98%) delete mode 100644 ee/app/graphql/resolvers/remote_development/agents_for_namespace_resolver.rb create mode 100644 ee/app/graphql/resolvers/remote_development/cluster_agent/remote_development_agent_config_resolver.rb create mode 100644 ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_agent_config_resolver.rb create mode 100644 ee/app/graphql/resolvers/remote_development/cluster_agent/workspaces_resolver.rb create mode 100644 ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb delete mode 100644 ee/app/graphql/resolvers/remote_development/remote_development_agent_config_for_agent_resolver.rb delete mode 100644 ee/app/graphql/resolvers/remote_development/workspaces_agent_config_for_agent_resolver.rb delete mode 100644 ee/app/graphql/resolvers/remote_development/workspaces_for_agent_resolver.rb rename ee/app/graphql/resolvers/remote_development/{workspaces_for_current_user_resolver.rb => workspaces_resolver.rb} (96%) create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/TODO_DEPRECATED.md create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/shared.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/with_no_args_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/shared.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/with_no_args_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/TODO_DEPRECATED.md delete mode 100644 ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/remote_development_agent_config_spec.rb delete mode 100644 ee/spec/requests/api/graphql/remote_development/namespace/remote_development_cluster_agents/workspaces_agent_config_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/shared.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_available_filter_arg_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_directly_mapped_filter_arg_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/namespace/workspaces_cluster_agents/with_unmapped_filter_arg_spec.rb create mode 100644 ee/spec/requests/api/graphql/remote_development/workspace/shared.rb diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index b056b50d16d9dd..bfd733ea6bd80f 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.7. +Use workspacesClusterAgents instead. + Returns [`ClusterAgentConnection`](#clusteragentconnection). This field returns a [connection](#connections). It accepts the @@ -25966,6 +25970,22 @@ 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. + +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 +29654,10 @@ four standard [pagination arguments](#pagination-arguments): Cluster agents in the namespace with remote development capabilities. +DETAILS: +**Deprecated** in GitLab 17.7. +Use workspacesClusterAgents instead. + Returns [`ClusterAgentConnection`](#clusteragentconnection). This field returns a [connection](#connections). It accepts the @@ -29763,6 +29787,22 @@ 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. + +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 7eedf70acce6c4..892ad043265d3d 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 7037ba7b7eef04..ea5a9f524e8c4e 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 71c5e0d0f36d81..f16a35e5342bb8 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 8a888ad6d33d81..593e5990728b80 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 instead', milestone: '17.7' }, 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,14 @@ 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', + 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 a59094139f785d..4b9aa30bd8b638 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 b2d035b5437397..e9b815c09d0836 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 6009e607f052d9..ff856113f96377 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 d67df22afa4145..00000000000000 --- 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 00000000000000..46a91d17d4ce33 --- /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 00000000000000..908f9dc7a5d706 --- /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 00000000000000..68c04db7e4506d --- /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 00000000000000..23025e00b6d77a --- /dev/null +++ b/ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb @@ -0,0 +1,33 @@ +# 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 + ) + rescue Gitlab::Access::AccessDeniedError + raise_resource_not_available_error! + 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 771f6fa669e8e4..00000000000000 --- 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 5ff7a6388851dc..00000000000000 --- 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 880535807c02d9..00000000000000 --- 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 1d79ba49357c7b..c8fd4f3d76efee 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 cc126c83e74946..d04fa9b138a329 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 169ec5a1d4d5e8..3ff788097fbdd4 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 eaeefd57c354b5..cd268975d487e1 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 40b18a371f8364..38e3807bd16afc 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 00000000000000..2fed024ee29ee6 --- /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 00000000000000..4b9bf1311882aa --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/remote_development_agent_config/shared.rb @@ -0,0 +1,91 @@ +# 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(:user).tap do |user| + # create the minimum privileged user that should have the project and namespace + # permissions to access the remote_development_agent_config. + project.namespace.add_member(user, :developer) + 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.namespace.add_member(user, :reporter) + end + 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 00000000000000..4860cac5d29e89 --- /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 3d5777fc65b7fd..91b15e500f4928 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 00000000000000..32d70aa73af603 --- /dev/null +++ b/ee/spec/requests/api/graphql/remote_development/cluster_agent/workspaces_agent_config/shared.rb @@ -0,0 +1,88 @@ +# 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(:user).tap do |user| + # create the minimum privileged user that should have the project and namespace + # permissions to access the workspaces_agent_config. + project.namespace.add_member(user, :developer) + 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.namespace.add_member(user, :reporter) + end + 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 00000000000000..72db39b7677483 --- /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 a57a35e4d8ae2f..ea53ac8916900c 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 00000000000000..9e4a99f59ce957 --- /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 c0fecd1883f2ef..00000000000000 --- 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 ce2105928dd2d6..fa6220b7ba7847 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 5070cc13212628..6915a0c645c1f8 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 0d3d602a3732f5..3296e20f3cd3bf 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 6f8e8bd6e190c0..16cee30710f391 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 7cb936b6fe3345..00000000000000 --- 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 00000000000000..ffd020990191ee --- /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 00000000000000..ac2c7b256f8fde --- /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 00000000000000..c0db7505acc9d9 --- /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 00000000000000..9246ca1598448b --- /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 ce4c26aa9e214e..f3ad366f35fa76 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 00000000000000..bf7b532ebd1f14 --- /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 29aa4a19ec45dd..dbf8f4a4f3a3aa 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 57500dca65b137..dde1f92233e1e6 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 561cea8f7f8e43..f2c43baa3235bb 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 57dbd2b02f9dbf..826dee564e592a 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: -- GitLab From ba4d84cc942393fff6de3d62ed93190e54b9e9b5 Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Tue, 24 Dec 2024 19:19:17 +0530 Subject: [PATCH 2/3] Address review feedback --- doc/api/graphql/reference/index.md | 8 ++++---- ee/app/graphql/ee/types/namespace_type.rb | 2 +- .../namespace/cluster_agents_resolver.rb | 2 -- .../api/graphql/remote_development/README.md | 2 +- .../remote_development_agent_config/shared.rb | 12 ++++-------- .../with_no_args_spec.rb | 2 +- .../cluster_agent/workspaces_agent_config/shared.rb | 12 ++++-------- .../workspaces_agent_config/with_no_args_spec.rb | 2 +- 8 files changed, 16 insertions(+), 26 deletions(-) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index bfd733ea6bd80f..093f86d244c2e7 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -25482,8 +25482,8 @@ four standard [pagination arguments](#pagination-arguments): Cluster agents in the namespace with remote development capabilities. DETAILS: -**Deprecated** in GitLab 17.7. -Use workspacesClusterAgents instead. +**Deprecated** in GitLab 17.8. +Use `workspacesClusterAgents`. Returns [`ClusterAgentConnection`](#clusteragentconnection). @@ -29655,8 +29655,8 @@ four standard [pagination arguments](#pagination-arguments): Cluster agents in the namespace with remote development capabilities. DETAILS: -**Deprecated** in GitLab 17.7. -Use workspacesClusterAgents instead. +**Deprecated** in GitLab 17.8. +Use `workspacesClusterAgents`. Returns [`ClusterAgentConnection`](#clusteragentconnection). diff --git a/ee/app/graphql/ee/types/namespace_type.rb b/ee/app/graphql/ee/types/namespace_type.rb index 593e5990728b80..2e6c5361df3103 100644 --- a/ee/app/graphql/ee/types/namespace_type.rb +++ b/ee/app/graphql/ee/types/namespace_type.rb @@ -136,7 +136,7 @@ module NamespaceType field :remote_development_cluster_agents, ::Types::Clusters::AgentType.connection_type, - deprecated: { reason: 'Use workspacesClusterAgents instead', milestone: '17.7' }, + deprecated: { reason: 'Use `workspacesClusterAgents`', milestone: '17.8' }, extras: [:lookahead], null: true, description: 'Cluster agents in the namespace with remote development capabilities', 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 index 23025e00b6d77a..1ee5baad49f905 100644 --- a/ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb +++ b/ee/app/graphql/resolvers/remote_development/namespace/cluster_agents_resolver.rb @@ -24,8 +24,6 @@ def resolve(**args) filter: args[:filter].downcase.to_sym, user: current_user ) - rescue Gitlab::Access::AccessDeniedError - raise_resource_not_available_error! 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 38e3807bd16afc..b7e0d69d659c31 100644 --- a/ee/spec/requests/api/graphql/remote_development/README.md +++ b/ee/spec/requests/api/graphql/remote_development/README.md @@ -88,5 +88,5 @@ 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 +- whether they do proper authorization - etc. 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 index 4b9bf1311882aa..abd965c0dc7b7d 100644 --- 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 @@ -10,19 +10,15 @@ let_it_be(:remote_development_agent_config) { create(:workspaces_agent_config, agent: agent) } 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 remote_development_agent_config. - project.namespace.add_member(user, :developer) - end + # 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).tap do |user| - project.namespace.add_member(user, :reporter) - end + create(:user, reporter_of: project.namespace) end let_it_be(:unauthorized_remote_development_agent_config) { create(:workspaces_agent_config) } 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 index 4860cac5d29e89..bdcab3be295595 100644 --- 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 @@ -3,7 +3,7 @@ require 'spec_helper' require_relative './shared' -# NOTE: Even though this single-remoteDevelopmentAgentConfig spec only has no fields to test, we still use similar +# 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 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 index 32d70aa73af603..d9c0f35dae601a 100644 --- 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 @@ -10,19 +10,15 @@ let_it_be(:workspaces_agent_config) { create(:workspaces_agent_config, agent: agent) } 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 workspaces_agent_config. - project.namespace.add_member(user, :developer) - end + # 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).tap do |user| - project.namespace.add_member(user, :reporter) - end + create(:user, reporter_of: project.namespace) end let_it_be(:unauthorized_workspaces_agent_config) { create(:workspaces_agent_config) } 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 index 72db39b7677483..214e929168a938 100644 --- 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 @@ -3,7 +3,7 @@ require 'spec_helper' require_relative './shared' -# NOTE: Even though this single-workspacesAgentConfig spec only has no fields to test, we still use similar +# 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 -- GitLab From fb4af4f9249d7392fb58b9f55fde358ded4bc70e Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Thu, 2 Jan 2025 15:44:28 -0700 Subject: [PATCH 3/3] Review feedback - make new graphql field experimental --- doc/api/graphql/reference/index.md | 8 ++++++++ ee/app/graphql/ee/types/namespace_type.rb | 1 + 2 files changed, 9 insertions(+) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 093f86d244c2e7..774de53928981a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -25974,6 +25974,10 @@ four standard [pagination arguments](#pagination-arguments): 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 @@ -29791,6 +29795,10 @@ four standard [pagination arguments](#pagination-arguments): 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 diff --git a/ee/app/graphql/ee/types/namespace_type.rb b/ee/app/graphql/ee/types/namespace_type.rb index 2e6c5361df3103..4d2184909a456e 100644 --- a/ee/app/graphql/ee/types/namespace_type.rb +++ b/ee/app/graphql/ee/types/namespace_type.rb @@ -154,6 +154,7 @@ module NamespaceType 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 -- GitLab