diff options
Diffstat (limited to 'spec/support/helpers')
21 files changed, 564 insertions, 184 deletions
diff --git a/spec/support/helpers/after_next_helpers.rb b/spec/support/helpers/after_next_helpers.rb new file mode 100644 index 00000000000..0a7844fdd8f --- /dev/null +++ b/spec/support/helpers/after_next_helpers.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require_relative './next_instance_of' + +module AfterNextHelpers + class DeferredExpectation + include ::NextInstanceOf + include ::RSpec::Matchers + include ::RSpec::Mocks::ExampleMethods + + def initialize(klass, args, level:) + @klass = klass + @args = args + @level = level.to_sym + end + + def to(condition) + run_condition(condition, asserted: true) + end + + def not_to(condition) + run_condition(condition, asserted: false) + end + + private + + attr_reader :klass, :args, :level + + def run_condition(condition, asserted:) + msg = asserted ? :to : :not_to + case level + when :expect + if asserted + expect_next_instance_of(klass, *args) { |instance| expect(instance).send(msg, condition) } + else + allow_next_instance_of(klass, *args) { |instance| expect(instance).send(msg, condition) } + end + when :allow + allow_next_instance_of(klass, *args) { |instance| allow(instance).send(msg, condition) } + else + raise "Unknown level: #{level}" + end + end + end + + def allow_next(klass, *args) + DeferredExpectation.new(klass, args, level: :allow) + end + + def expect_next(klass, *args) + DeferredExpectation.new(klass, args, level: :expect) + end +end diff --git a/spec/support/helpers/database_helpers.rb b/spec/support/helpers/database_helpers.rb new file mode 100644 index 00000000000..e9f0a74a8d1 --- /dev/null +++ b/spec/support/helpers/database_helpers.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module DatabaseHelpers + # In order to directly work with views using factories, + # we can swapout the view for a table of identical structure. + def swapout_view_for_table(view) + ActiveRecord::Base.connection.execute(<<~SQL) + CREATE TABLE #{view}_copy (LIKE #{view}); + DROP VIEW #{view}; + ALTER TABLE #{view}_copy RENAME TO #{view}; + SQL + end +end diff --git a/spec/support/helpers/dependency_proxy_helpers.rb b/spec/support/helpers/dependency_proxy_helpers.rb index 545b9d1f4d0..ebb849628bf 100644 --- a/spec/support/helpers/dependency_proxy_helpers.rb +++ b/spec/support/helpers/dependency_proxy_helpers.rb @@ -11,11 +11,18 @@ module DependencyProxyHelpers .to_return(status: status, body: body || auth_body) end - def stub_manifest_download(image, tag, status = 200, body = nil) + def stub_manifest_download(image, tag, status: 200, body: nil, headers: {}) manifest_url = registry.manifest_url(image, tag) stub_full_request(manifest_url) - .to_return(status: status, body: body || manifest) + .to_return(status: status, body: body || manifest, headers: headers) + end + + def stub_manifest_head(image, tag, status: 200, body: nil, digest: '123456') + manifest_url = registry.manifest_url(image, tag) + + stub_full_request(manifest_url, method: :head) + .to_return(status: status, body: body, headers: { 'docker-content-digest' => digest } ) end def stub_blob_download(image, blob_sha, status = 200, body = '123456') @@ -25,6 +32,13 @@ module DependencyProxyHelpers .to_return(status: status, body: body) end + def build_jwt(user = nil, expire_time: nil) + JSONWebToken::HMACToken.new(::Auth::DependencyProxyAuthenticationService.secret).tap do |jwt| + jwt['user_id'] = user.id if user + jwt.expire_time = expire_time || jwt.issued_at + 1.minute + end + end + private def registry diff --git a/spec/support/helpers/features/merge_request_helpers.rb b/spec/support/helpers/features/merge_request_helpers.rb new file mode 100644 index 00000000000..53896e1fe12 --- /dev/null +++ b/spec/support/helpers/features/merge_request_helpers.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Spec + module Support + module Helpers + module Features + module MergeRequestHelpers + def preload_view_requirements(merge_request, note) + # This will load the status fields of the author of the note and merge request + # to avoid queries when rendering the view being tested. + # + merge_request.author.status + note.author.status + end + + def serialize_issuable_sidebar(user, project, merge_request) + MergeRequestSerializer + .new(current_user: user, project: project) + .represent(merge_request, serializer: 'sidebar') + end + end + end + end + end +end diff --git a/spec/support/helpers/features/web_ide_spec_helpers.rb b/spec/support/helpers/features/web_ide_spec_helpers.rb index 12d3cecd052..358bfacce05 100644 --- a/spec/support/helpers/features/web_ide_spec_helpers.rb +++ b/spec/support/helpers/features/web_ide_spec_helpers.rb @@ -22,8 +22,6 @@ module WebIdeSpecHelpers click_link('Web IDE') wait_for_requests - - save_monaco_editor_reference end def ide_tree_body @@ -65,17 +63,6 @@ module WebIdeSpecHelpers ide_set_editor_value(content) end - def ide_rename_file(path, new_path) - container = ide_traverse_to_file(path) - - click_file_action(container, 'Rename/Move') - - within '#ide-new-entry' do - find('input').fill_in(with: new_path) - click_button('Rename file') - end - end - # Deletes a file by traversing to `path` # then clicking the 'Delete' action. # @@ -103,20 +90,6 @@ module WebIdeSpecHelpers container end - def ide_close_file(name) - within page.find('.multi-file-tabs') do - click_button("Close #{name}") - end - end - - def ide_open_file(path) - row = ide_traverse_to_file(path) - - ide_open_file_row(row) - - wait_for_requests - end - def ide_open_file_row(row) return if ide_folder_row_open?(row) @@ -130,10 +103,6 @@ module WebIdeSpecHelpers execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')") end - def ide_set_editor_position(line, col) - execute_script("TEST_EDITOR.setPosition(#{{ lineNumber: line, column: col }.to_json})") - end - def ide_editor_value editor = find('.monaco-editor') uri = editor['data-uri'] @@ -180,8 +149,4 @@ module WebIdeSpecHelpers wait_for_requests end end - - def save_monaco_editor_reference - evaluate_script("monaco.editor.onDidCreateEditor(editor => { window.TEST_EDITOR = editor; })") - end end diff --git a/spec/support/helpers/file_read_helpers.rb b/spec/support/helpers/file_read_helpers.rb new file mode 100644 index 00000000000..c30a9e6466f --- /dev/null +++ b/spec/support/helpers/file_read_helpers.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module FileReadHelpers + def stub_file_read(file, content: nil, error: nil) + allow_original_file_read + + expectation = allow(File).to receive(:read).with(file) + + if error + expectation.and_raise(error) + elsif content + expectation.and_return(content) + else + expectation + end + end + + def expect_file_read(file, content: nil, error: nil) + allow_original_file_read + + expectation = expect(File).to receive(:read).with(file) + + if error + expectation.and_raise(error) + elsif content + expectation.and_return(content) + else + expectation + end + end + + def expect_file_not_to_read(file) + allow_original_file_read + + expect(File).not_to receive(:read).with(file) + end + + private + + def allow_original_file_read + # Don't set this mock twice, otherwise subsequent calls will clobber + # previous mocks + return if @allow_original_file_read + + @allow_original_file_read = true + allow(File).to receive(:read).and_call_original + end +end diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index d203ff60cc9..10068b9c508 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -113,6 +113,10 @@ module FilteredSearchHelpers create_token('Assignee', assignee_name) end + def reviewer_token(reviewer_name = nil) + create_token('Reviewer', reviewer_name) + end + def milestone_token(milestone_name = nil, has_symbol = true, operator = '=') symbol = has_symbol ? '%' : nil create_token('Milestone', milestone_name, symbol, operator) diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb index c9c1c4dcfc9..c9b4e06f067 100644 --- a/spec/support/helpers/git_http_helpers.rb +++ b/spec/support/helpers/git_http_helpers.rb @@ -5,45 +5,45 @@ require_relative 'workhorse_helpers' module GitHttpHelpers include WorkhorseHelpers - def clone_get(project, **options) - get "/#{project}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) + def clone_get(repository_path, **options) + get "/#{repository_path}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end - def clone_post(project, **options) - post "/#{project}/git-upload-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) + def clone_post(repository_path, **options) + post "/#{repository_path}/git-upload-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end - def push_get(project, **options) - get "/#{project}/info/refs", params: { service: 'git-receive-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) + def push_get(repository_path, **options) + get "/#{repository_path}/info/refs", params: { service: 'git-receive-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end - def push_post(project, **options) - post "/#{project}/git-receive-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) + def push_post(repository_path, **options) + post "/#{repository_path}/git-receive-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end - def download(project, user: nil, password: nil, spnego_request_token: nil) + def download(repository_path, user: nil, password: nil, spnego_request_token: nil) args = { user: user, password: password, spnego_request_token: spnego_request_token } - clone_get(project, **args) + clone_get(repository_path, **args) yield response - clone_post(project, **args) + clone_post(repository_path, **args) yield response end - def upload(project, user: nil, password: nil, spnego_request_token: nil) + def upload(repository_path, user: nil, password: nil, spnego_request_token: nil) args = { user: user, password: password, spnego_request_token: spnego_request_token } - push_get(project, **args) + push_get(repository_path, **args) yield response - push_post(project, **args) + push_post(repository_path, **args) yield response end - def download_or_upload(project, **args, &block) - download(project, **args, &block) - upload(project, **args, &block) + def download_or_upload(repository_path, **args, &block) + download(repository_path, **args, &block) + upload(repository_path, **args, &block) end def auth_env(user, password, spnego_request_token) diff --git a/spec/support/helpers/gitlab_verify_helpers.rb b/spec/support/helpers/gitlab_verify_helpers.rb index 9901ce374ed..2a6ba8aaff4 100644 --- a/spec/support/helpers/gitlab_verify_helpers.rb +++ b/spec/support/helpers/gitlab_verify_helpers.rb @@ -2,7 +2,7 @@ module GitlabVerifyHelpers def collect_ranges(args = {}) - verifier = described_class.new(args.merge(batch_size: 1)) + verifier = described_class.new(**args.merge(batch_size: 1)) collect_results(verifier).map { |range, _| range } end diff --git a/spec/support/helpers/gpg_helpers.rb b/spec/support/helpers/gpg_helpers.rb index f4df1cf601c..389e5818dbe 100644 --- a/spec/support/helpers/gpg_helpers.rb +++ b/spec/support/helpers/gpg_helpers.rb @@ -144,6 +144,145 @@ module GpgHelpers '5F7EA3981A5845B141ABD522CCFBE19F00AC8B1D' end + def secret_key2 + <<~KEY.strip + -----BEGIN PGP PRIVATE KEY BLOCK----- + + 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 + 4f6xMfFu2A== + =3YL6 + -----END PGP PRIVATE KEY BLOCK----- + KEY + end + + def public_key2 + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + + 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 + =RAwd + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + + def fingerprint2 + 'C447A6F6BFD9CEF8FB371785571625A930241179' + end + def names ['Nannie Bernhard'] end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index a1b4e6eee92..b20801bd3c4 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -4,6 +4,11 @@ module GraphqlHelpers MutationDefinition = Struct.new(:query, :variables) NoData = Class.new(StandardError) + UnauthorizedObject = Class.new(StandardError) + + def graphql_args(**values) + ::Graphql::Arguments.new(values) + end # makes an underscored string look like a fieldname # "merge_request" => "mergeRequest" @@ -17,7 +22,10 @@ module GraphqlHelpers # ready, then the early return is returned instead. # # Then the resolve method is called. - def resolve(resolver_class, args: {}, **resolver_args) + def resolve(resolver_class, args: {}, lookahead: :not_given, parent: :not_given, **resolver_args) + args = aliased_args(resolver_class, args) + args[:parent] = parent unless parent == :not_given + args[:lookahead] = lookahead unless lookahead == :not_given resolver = resolver_instance(resolver_class, **resolver_args) ready, early_return = sync_all { resolver.ready?(**args) } @@ -26,6 +34,16 @@ module GraphqlHelpers resolver.resolve(**args) end + # TODO: Remove this method entirely when GraphqlHelpers uses real resolve_field + # see: https://gitlab.com/gitlab-org/gitlab/-/issues/287791 + def aliased_args(resolver, args) + definitions = resolver.arguments + + args.transform_keys do |k| + definitions[GraphqlHelpers.fieldnamerize(k)]&.keyword || k + end + end + def resolver_instance(resolver_class, obj: nil, ctx: {}, field: nil, schema: GitlabSchema) if ctx.is_a?(Hash) q = double('Query', schema: schema) @@ -111,24 +129,33 @@ module GraphqlHelpers def variables_for_mutation(name, input) graphql_input = prepare_input_for_mutation(input) - result = { input_variable_name_for_mutation(name) => graphql_input } + { input_variable_name_for_mutation(name) => graphql_input } + end - # Avoid trying to serialize multipart data into JSON - if graphql_input.values.none? { |value| io_value?(value) } - result.to_json - else - result - end + def serialize_variables(variables) + return unless variables + return variables if variables.is_a?(String) + + ::Gitlab::Utils::MergeHash.merge(Array.wrap(variables).map(&:to_h)).to_json end - def resolve_field(name, object, args = {}) - context = double("Context", - schema: GitlabSchema, - query: GraphQL::Query.new(GitlabSchema), - parent: nil) - field = described_class.fields[name] + def resolve_field(name, object, args = {}, current_user: nil) + q = GraphQL::Query.new(GitlabSchema) + context = GraphQL::Query::Context.new(query: q, object: object, values: { current_user: current_user }) + allow(context).to receive(:parent).and_return(nil) + field = described_class.fields.fetch(GraphqlHelpers.fieldnamerize(name)) instance = described_class.authorized_new(object, context) - field.resolve_field(instance, {}, context) + raise UnauthorizedObject unless instance + + field.resolve_field(instance, args, context) + end + + def simple_resolver(resolved_value = 'Resolved value') + Class.new(Resolvers::BaseResolver) do + define_method :resolve do |**_args| + resolved_value + end + end end # Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys @@ -165,10 +192,32 @@ module GraphqlHelpers end def query_graphql_field(name, attributes = {}, fields = nil) - <<~QUERY - #{field_with_params(name, attributes)} - #{wrap_fields(fields || all_graphql_fields_for(name.to_s.classify))} - QUERY + attributes, fields = [nil, attributes] if fields.nil? && !attributes.is_a?(Hash) + + field = field_with_params(name, attributes) + + field + wrap_fields(fields || all_graphql_fields_for(name.to_s.classify)).to_s + end + + def page_info_selection + "pageInfo { hasNextPage hasPreviousPage endCursor startCursor }" + end + + def query_nodes(name, fields = nil, args: nil, of: name, include_pagination_info: false, max_depth: 1) + fields ||= all_graphql_fields_for(of.to_s.classify, max_depth: max_depth) + node_selection = include_pagination_info ? "#{page_info_selection} nodes" : :nodes + query_graphql_path([[name, args], node_selection], fields) + end + + # e.g: + # query_graphql_path(%i[foo bar baz], all_graphql_fields_for('Baz')) + # => foo { bar { baz { x y z } } } + def query_graphql_path(segments, fields = nil) + # we really want foldr here... + segments.reverse.reduce(fields) do |tail, segment| + name, args = Array.wrap(segment) + query_graphql_field(name, args, tail) + end end def wrap_fields(fields) @@ -203,50 +252,22 @@ module GraphqlHelpers type = GitlabSchema.types[class_name.to_s] return "" unless type - type.fields.map do |name, field| - # We can't guess arguments, so skip fields that require them - next if required_arguments?(field) - next if excluded.include?(name) - - singular_field_type = field_type(field) - - # If field type is the same as parent type, then we're hitting into - # mutual dependency. Break it from infinite recursion - next if parent_types.include?(singular_field_type) + # We can't guess arguments, so skip fields that require them + skip = ->(name, field) { excluded.include?(name) || required_arguments?(field) } - if nested_fields?(field) - fields = - all_graphql_fields_for(singular_field_type, parent_types | [type], max_depth: max_depth - 1) - - "#{name} { #{fields} }" unless fields.blank? - else - name - end - end.compact.join("\n") + ::Graphql::FieldSelection.select_fields(type, skip, parent_types, max_depth) end - def attributes_to_graphql(attributes) - attributes.map do |name, value| - value_str = as_graphql_literal(value) + def with_signature(variables, query) + %Q[query(#{variables.map(&:sig).join(', ')}) #{query}] + end - "#{GraphqlHelpers.fieldnamerize(name.to_s)}: #{value_str}" - end.join(", ") + def var(type) + ::Graphql::Var.new(generate(:variable), type) end - # Fairly dumb Ruby => GraphQL rendering function. Only suitable for testing. - # Use symbol for Enum values - def as_graphql_literal(value) - case value - when Array then "[#{value.map { |v| as_graphql_literal(v) }.join(',')}]" - when Hash then "{#{attributes_to_graphql(value)}}" - when Integer, Float then value.to_s - when String then "\"#{value.gsub(/"/, '\\"')}\"" - when Symbol then value - when nil then 'null' - when true then 'true' - when false then 'false' - else raise ArgumentError, "Cannot represent #{value} as GraphQL literal" - end + def attributes_to_graphql(arguments) + ::Graphql::Arguments.new(arguments).to_s end def post_multiplex(queries, current_user: nil, headers: {}) @@ -254,7 +275,7 @@ module GraphqlHelpers end def post_graphql(query, current_user: nil, variables: nil, headers: {}) - params = { query: query, variables: variables&.to_json } + params = { query: query, variables: serialize_variables(variables) } post api('/', current_user, version: 'graphql'), params: params, headers: headers end @@ -320,36 +341,47 @@ module GraphqlHelpers { operations: operations.to_json, map: map.to_json }.merge(extracted_files) end + def fresh_response_data + Gitlab::Json.parse(response.body) + end + # Raises an error if no data is found - def graphql_data + def graphql_data(body = json_response) # Note that `json_response` is defined as `let(:json_response)` and # therefore, in a spec with multiple queries, will only contain data # from the _first_ query, not subsequent ones - json_response['data'] || (raise NoData, graphql_errors) + body['data'] || (raise NoData, graphql_errors(body)) end def graphql_data_at(*path) graphql_dig_at(graphql_data, *path) end + # Slightly more powerful than just `dig`: + # - also supports implicit flat-mapping (.e.g. :foo :nodes :bar :nodes) def graphql_dig_at(data, *path) keys = path.map { |segment| segment.is_a?(Integer) ? segment : GraphqlHelpers.fieldnamerize(segment) } # Allows for array indexing, like this # ['project', 'boards', 'edges', 0, 'node', 'lists'] keys.reduce(data) do |memo, key| - memo.is_a?(Array) ? memo[key] : memo&.dig(key) + if memo.is_a?(Array) + key.is_a?(Integer) ? memo[key] : memo.flat_map { |e| Array.wrap(e[key]) } + else + memo&.dig(key) + end end end - def graphql_errors - case json_response + # See note at graphql_data about memoization and multiple requests + def graphql_errors(body = json_response) + case body when Hash # regular query - json_response['errors'] + body['errors'] when Array # multiplexed queries - json_response.map { |response| response['errors'] } + body.map { |response| response['errors'] } else - raise "Unknown GraphQL response type #{json_response.class}" + raise "Unknown GraphQL response type #{body.class}" end end @@ -392,19 +424,29 @@ module GraphqlHelpers end def nested_fields?(field) - !scalar?(field) && !enum?(field) + ::Graphql::FieldInspection.new(field).nested_fields? end def scalar?(field) - field_type(field).kind.scalar? + ::Graphql::FieldInspection.new(field).scalar? end def enum?(field) - field_type(field).kind.enum? + ::Graphql::FieldInspection.new(field).enum? end + # There are a few non BaseField fields in our schema (pageInfo for one). + # None of them require arguments. def required_arguments?(field) - field.arguments.values.any? { |argument| argument.type.non_null? } + return field.requires_argument? if field.is_a?(::Types::BaseField) + + if (meta = field.try(:metadata)) && meta[:type_class] + required_arguments?(meta[:type_class]) + elsif args = field.try(:arguments) + args.values.any? { |argument| argument.type.non_null? } + else + false + end end def io_value?(value) @@ -412,15 +454,7 @@ module GraphqlHelpers end def field_type(field) - field_type = field.type.respond_to?(:to_graphql) ? field.type.to_graphql : field.type - - # The type could be nested. For example `[GraphQL::STRING_TYPE]`: - # - List - # - String! - # - String - field_type = field_type.of_type while field_type.respond_to?(:of_type) - - field_type + ::Graphql::FieldInspection.new(field).type end # for most tests, we want to allow unlimited complexity @@ -498,6 +532,20 @@ module GraphqlHelpers variables: {} ) end + + # A lookahead that selects everything + def positive_lookahead + double(selects?: true).tap do |selection| + allow(selection).to receive(:selection).and_return(selection) + end + end + + # A lookahead that selects nothing + def negative_lookahead + double(selects?: false).tap do |selection| + allow(selection).to receive(:selection).and_return(selection) + end + end end # This warms our schema, doing this as part of loading the helpers to avoid diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index e21d4497cda..86022e16d71 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -138,13 +138,15 @@ module LoginHelpers secret: 'mock_secret' }, extra: { - raw_info: { - info: { - name: 'mockuser', - email: email, - image: 'mock_user_thumbnail_url' + raw_info: OneLogin::RubySaml::Attributes.new( + { + info: { + name: 'mockuser', + email: email, + image: 'mock_user_thumbnail_url' + } } - }, + ), response_object: response_object } }).merge(additional_info) { |_, old_hash, new_hash| old_hash.merge(new_hash) } @@ -198,7 +200,7 @@ module LoginHelpers env['omniauth.error.strategy'] = strategy end - def stub_omniauth_saml_config(messages, context: Rails.application) + def stub_omniauth_saml_config(context: Rails.application, **messages) set_devise_mapping(context: context) routes = Rails.application.routes routes.disable_clear_and_finalize = true diff --git a/spec/support/helpers/multipart_helpers.rb b/spec/support/helpers/multipart_helpers.rb index 2e8db0e9e42..bcb184f84c5 100644 --- a/spec/support/helpers/multipart_helpers.rb +++ b/spec/support/helpers/multipart_helpers.rb @@ -37,7 +37,7 @@ module MultipartHelpers # *without* the "#{key}." prefix result.deep_transform_keys! { |k| k.remove("#{key}.") } { - "#{key}.gitlab-workhorse-upload" => jwt_token('upload' => result) + "#{key}.gitlab-workhorse-upload" => jwt_token(data: { 'upload' => result }) } end diff --git a/spec/support/helpers/next_instance_of.rb b/spec/support/helpers/next_instance_of.rb index 83c788c3d38..a8e9ab2bafe 100644 --- a/spec/support/helpers/next_instance_of.rb +++ b/spec/support/helpers/next_instance_of.rb @@ -1,28 +1,31 @@ # frozen_string_literal: true module NextInstanceOf - def expect_next_instance_of(klass, *new_args) - stub_new(expect(klass), *new_args) do |expectation| - yield(expectation) - end + def expect_next_instance_of(klass, *new_args, &blk) + stub_new(expect(klass), nil, *new_args, &blk) end - def allow_next_instance_of(klass, *new_args) - stub_new(allow(klass), *new_args) do |allowance| - yield(allowance) - end + def expect_next_instances_of(klass, number, *new_args, &blk) + stub_new(expect(klass), number, *new_args, &blk) + end + + def allow_next_instance_of(klass, *new_args, &blk) + stub_new(allow(klass), nil, *new_args, &blk) + end + + def allow_next_instances_of(klass, number, *new_args, &blk) + stub_new(allow(klass), number, *new_args, &blk) end private - def stub_new(target, *new_args) + def stub_new(target, number, *new_args, &blk) receive_new = receive(:new) + receive_new.exactly(number).times if number receive_new.with(*new_args) if new_args.any? target.to receive_new.and_wrap_original do |method, *original_args| - method.call(*original_args).tap do |instance| - yield(instance) - end + method.call(*original_args).tap(&blk) end end end diff --git a/spec/support/helpers/services_helper.rb b/spec/support/helpers/services_helper.rb new file mode 100644 index 00000000000..bf007815551 --- /dev/null +++ b/spec/support/helpers/services_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative './after_next_helpers' + +module ServicesHelper + include AfterNextHelpers + + def expect_execution_of(service_class, *args) + expect_next(service_class, *args).to receive(:execute) + end +end diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb index 15eac1b24fc..70a4eadd8de 100644 --- a/spec/support/helpers/snowplow_helpers.rb +++ b/spec/support/helpers/snowplow_helpers.rb @@ -31,7 +31,31 @@ module SnowplowHelpers # ) # end # end - def expect_snowplow_event(category:, action:, **kwargs) + # + # Passing context: + # + # Simply provide a hash that has the schema and data expected. + # + # expect_snowplow_event( + # category: 'Experiment', + # action: 'created', + # context: [ + # { + # schema: 'iglu:com.gitlab/.../0-3-0', + # data: { key: 'value' } + # } + # ] + # ) + def expect_snowplow_event(category:, action:, context: nil, **kwargs) + if context + kwargs[:context] = [] + context.each do |c| + expect(SnowplowTracker::SelfDescribingJson).to have_received(:new) + .with(c[:schema], c[:data]).at_least(:once) + kwargs[:context] << an_instance_of(SnowplowTracker::SelfDescribingJson) + end + end + expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking .with(category, action, **kwargs).at_least(:once) end diff --git a/spec/support/helpers/stub_experiments.rb b/spec/support/helpers/stub_experiments.rb index 7a6154d5ef9..247692d83ee 100644 --- a/spec/support/helpers/stub_experiments.rb +++ b/spec/support/helpers/stub_experiments.rb @@ -3,15 +3,15 @@ module StubExperiments # Stub Experiment with `key: true/false` # - # @param [Hash] experiment where key is feature name and value is boolean whether enabled or not. + # @param [Hash] experiment where key is feature name and value is boolean whether active or not. # # Examples - # - `stub_experiment(signup_flow: false)` ... Disable `signup_flow` experiment globally. + # - `stub_experiment(signup_flow: false)` ... Disables `signup_flow` experiment. def stub_experiment(experiments) - allow(Gitlab::Experimentation).to receive(:enabled?).and_call_original + allow(Gitlab::Experimentation).to receive(:active?).and_call_original experiments.each do |experiment_key, enabled| - allow(Gitlab::Experimentation).to receive(:enabled?).with(experiment_key) { enabled } + allow(Gitlab::Experimentation).to receive(:active?).with(experiment_key) { enabled } end end @@ -20,12 +20,12 @@ module StubExperiments # @param [Hash] experiment where key is feature name and value is boolean whether enabled or not. # # Examples - # - `stub_experiment_for_user(signup_flow: false)` ... Disable `signup_flow` experiment for user. - def stub_experiment_for_user(experiments) - allow(Gitlab::Experimentation).to receive(:enabled_for_value?).and_call_original + # - `stub_experiment_for_subject(signup_flow: false)` ... Disable `signup_flow` experiment for user. + def stub_experiment_for_subject(experiments) + allow(Gitlab::Experimentation).to receive(:in_experiment_group?).and_call_original experiments.each do |experiment_key, enabled| - allow(Gitlab::Experimentation).to receive(:enabled_for_value?).with(experiment_key, anything) { enabled } + allow(Gitlab::Experimentation).to receive(:in_experiment_group?).with(experiment_key, anything) { enabled } end end end diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index dba3d2b137e..dc54a21d0fa 100644 --- a/spec/support/helpers/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -22,6 +22,16 @@ module StubObjectStorage background_upload: false, direct_upload: false ) + new_config = config.to_h.deep_symbolize_keys.merge({ + enabled: enabled, + proxy_download: proxy_download, + background_upload: background_upload, + direct_upload: direct_upload + }) + + # Needed for ObjectStorage::Config compatibility + allow(config).to receive(:to_hash).and_return(new_config) + allow(config).to receive(:to_h).and_return(new_config) allow(config).to receive(:enabled) { enabled } allow(config).to receive(:proxy_download) { proxy_download } allow(config).to receive(:background_upload) { background_upload } @@ -84,13 +94,6 @@ module StubObjectStorage def stub_terraform_state_object_storage(**params) stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store, - uploader: Terraform::VersionedStateUploader, - remote_directory: 'terraform', - **params) - end - - def stub_terraform_state_version_object_storage(**params) - stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store, uploader: Terraform::StateUploader, remote_directory: 'terraform', **params) diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 4c78ca0117c..01571277a1d 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -168,6 +168,11 @@ module TestEnv version: Gitlab::GitalyClient.expected_server_version, task: "gitlab:gitaly:install[#{install_gitaly_args}]") do Gitlab::SetupHelper::Gitaly.create_configuration(gitaly_dir, { 'default' => repos_path }, force: true) + Gitlab::SetupHelper::Gitaly.create_configuration( + gitaly_dir, + { 'default' => repos_path }, force: true, + options: { gitaly_socket: "gitaly2.socket", config_filename: "gitaly2.config.toml" } + ) Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true) end @@ -241,15 +246,38 @@ module TestEnv end def setup_workhorse - install_workhorse_args = [workhorse_dir, workhorse_url].compact.join(',') - - component_timed_setup( - 'GitLab Workhorse', - install_dir: workhorse_dir, - version: Gitlab::Workhorse.version, - task: "gitlab:workhorse:install[#{install_workhorse_args}]") do - Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil) - end + start = Time.now + return if skip_compile_workhorse? + + puts "\n==> Setting up GitLab Workhorse..." + + FileUtils.rm_rf(workhorse_dir) + Gitlab::SetupHelper::Workhorse.compile_into(workhorse_dir) + Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil) + + File.write(workhorse_tree_file, workhorse_tree) if workhorse_source_clean? + + puts " GitLab Workhorse set up in #{Time.now - start} seconds...\n" + end + + def skip_compile_workhorse? + File.directory?(workhorse_dir) && + workhorse_source_clean? && + File.exist?(workhorse_tree_file) && + workhorse_tree == File.read(workhorse_tree_file) + end + + def workhorse_source_clean? + out = IO.popen(%w[git status --porcelain workhorse], &:read) + $?.success? && out.empty? + end + + def workhorse_tree + IO.popen(%w[git rev-parse HEAD:workhorse], &:read) + end + + def workhorse_tree_file + File.join(workhorse_dir, 'WORKHORSE_TREE') end def workhorse_dir @@ -260,7 +288,7 @@ module TestEnv host = "[#{host}]" if host.include?(':') listen_addr = [host, port].join(':') - config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir) + config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir, {}) # This should be set up in setup_workhorse, but since # component_needs_update? only checks that versions are consistent, diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index 8e8aeea2ea1..df79049123d 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -85,6 +85,7 @@ module UsageDataHelpers projects projects_imported_from_github projects_asana_active + projects_jenkins_active projects_jira_active projects_jira_server_active projects_jira_cloud_active @@ -161,7 +162,6 @@ module UsageDataHelpers git gitaly database - avg_cycle_analytics prometheus_metrics_enabled web_ide_clientside_preview_enabled ingress_modsecurity_enabled diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb index 7e95f49aea2..cd8387de686 100644 --- a/spec/support/helpers/workhorse_helpers.rb +++ b/spec/support/helpers/workhorse_helpers.rb @@ -85,15 +85,15 @@ module WorkhorseHelpers return {} if upload_params.empty? - { "#{key}.gitlab-workhorse-upload" => jwt_token('upload' => upload_params) } + { "#{key}.gitlab-workhorse-upload" => jwt_token(data: { 'upload' => upload_params }) } end - def jwt_token(data = {}, issuer: 'gitlab-workhorse', secret: Gitlab::Workhorse.secret, algorithm: 'HS256') + def jwt_token(data: {}, issuer: 'gitlab-workhorse', secret: Gitlab::Workhorse.secret, algorithm: 'HS256') JWT.encode({ 'iss' => issuer }.merge(data), secret, algorithm) end def workhorse_rewritten_fields_header(fields) - { Gitlab::Middleware::Multipart::RACK_ENV_KEY => jwt_token('rewritten_fields' => fields) } + { Gitlab::Middleware::Multipart::RACK_ENV_KEY => jwt_token(data: { 'rewritten_fields' => fields }) } end def workhorse_disk_accelerated_file_params(key, file) |