diff options
Diffstat (limited to 'spec/requests/api/graphql/project')
11 files changed, 361 insertions, 91 deletions
diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb index 7e32f54bf1d..6b1c8689515 100644 --- a/spec/requests/api/graphql/project/container_repositories_spec.rb +++ b/spec/requests/api/graphql/project/container_repositories_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'getting container repositories in a project' do let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten } let_it_be(:container_expiration_policy) { project.container_expiration_policy } - let(:fields) do + let(:container_repositories_fields) do <<~GQL edges { node { @@ -22,17 +22,25 @@ RSpec.describe 'getting container repositories in a project' do GQL end + let(:fields) do + <<~GQL + #{query_graphql_field('container_repositories', {}, container_repositories_fields)} + containerRepositoriesCount + GQL + end + let(:query) do graphql_query_for( 'project', { 'fullPath' => project.full_path }, - query_graphql_field('container_repositories', {}, fields) + fields ) end let(:user) { project.owner } let(:variables) { {} } let(:container_repositories_response) { graphql_data.dig('project', 'containerRepositories', 'edges') } + let(:container_repositories_count_response) { graphql_data.dig('project', 'containerRepositoriesCount') } before do stub_container_registry_config(enabled: true) @@ -100,7 +108,7 @@ RSpec.describe 'getting container repositories in a project' do <<~GQL query($path: ID!, $n: Int) { project(fullPath: $path) { - containerRepositories(first: $n) { #{fields} } + containerRepositories(first: $n) { #{container_repositories_fields} } } } GQL @@ -121,7 +129,7 @@ RSpec.describe 'getting container repositories in a project' do <<~GQL query($path: ID!, $name: String) { project(fullPath: $path) { - containerRepositories(name: $name) { #{fields} } + containerRepositories(name: $name) { #{container_repositories_fields} } } } GQL @@ -142,4 +150,10 @@ RSpec.describe 'getting container repositories in a project' do expect(container_repositories_response.first.dig('node', 'id')).to eq(container_repository.to_global_id.to_s) end end + + it 'returns the total count of container repositories' do + subject + + expect(container_repositories_count_response).to eq(container_repositories.size) + end end diff --git a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb index 4bce3c7fe0f..f544d78ecbb 100644 --- a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb +++ b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb @@ -42,7 +42,6 @@ RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha) describe 'scalar fields' do let(:path) { path_prefix } - let(:version_fields) { query_graphql_field(:sha) } before do post_query @@ -50,7 +49,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha) { id: ->(x) { x.to_global_id.to_s }, sha: ->(x) { x.sha } }.each do |field, value| describe ".#{field}" do - let(:version_fields) { query_graphql_field(field) } + let(:version_fields) { field } it "retrieves the #{field}" do expect(data).to match(a_hash_including(field.to_s => value[version])) diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb index 5f368833181..ddf63a8f2c9 100644 --- a/spec/requests/api/graphql/project/issue_spec.rb +++ b/spec/requests/api/graphql/project/issue_spec.rb @@ -29,8 +29,8 @@ RSpec.describe 'Query.project(fullPath).issue(iid)' do let(:design_fields) do [ - query_graphql_field(:filename), - query_graphql_field(:project, nil, query_graphql_field(:id)) + :filename, + query_graphql_field(:project, :id) ] end @@ -173,7 +173,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid)' do let(:result_fields) { { 'version' => id_hash(version) } } let(:object_fields) do - design_fields + [query_graphql_field(:version, nil, query_graphql_field(:id))] + design_fields + [query_graphql_field(:version, :id)] end let(:no_argument_error) { missing_required_argument(path, :id) } diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index 4f27f08bf98..9c915075c42 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -142,16 +142,14 @@ RSpec.describe 'getting an issue list for a project' do describe 'sorting and pagination' do let_it_be(:data_path) { [:project, :issues] } - def pagination_query(params, page_info) - graphql_query_for( - 'project', - { 'fullPath' => sort_project.full_path }, - query_graphql_field('issues', params, "#{page_info} edges { node { iid dueDate} }") + def pagination_query(params) + graphql_query_for(:project, { full_path: sort_project.full_path }, + query_graphql_field(:issues, params, "#{page_info} nodes { iid }") ) end def pagination_results_data(data) - data.map { |issue| issue.dig('node', 'iid').to_i } + data.map { |issue| issue.dig('iid').to_i } end context 'when sorting by due date' do @@ -164,7 +162,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'DUE_DATE_ASC' } + let(:sort_param) { :DUE_DATE_ASC } let(:first_param) { 2 } let(:expected_results) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] } end @@ -172,7 +170,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when descending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'DUE_DATE_DESC' } + let(:sort_param) { :DUE_DATE_DESC } let(:first_param) { 2 } let(:expected_results) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] } end @@ -189,7 +187,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'RELATIVE_POSITION_ASC' } + let(:sort_param) { :RELATIVE_POSITION_ASC } let(:first_param) { 2 } let(:expected_results) { [relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid] } end @@ -209,7 +207,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'PRIORITY_ASC' } + let(:sort_param) { :PRIORITY_ASC } let(:first_param) { 2 } let(:expected_results) { [priority_issue3.iid, priority_issue1.iid, priority_issue2.iid, priority_issue4.iid] } end @@ -217,7 +215,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when descending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'PRIORITY_DESC' } + let(:sort_param) { :PRIORITY_DESC } let(:first_param) { 2 } let(:expected_results) { [priority_issue1.iid, priority_issue3.iid, priority_issue2.iid, priority_issue4.iid] } end @@ -236,7 +234,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'LABEL_PRIORITY_ASC' } + let(:sort_param) { :LABEL_PRIORITY_ASC } let(:first_param) { 2 } let(:expected_results) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] } end @@ -244,7 +242,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when descending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'LABEL_PRIORITY_DESC' } + let(:sort_param) { :LABEL_PRIORITY_DESC } let(:first_param) { 2 } let(:expected_results) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] } end @@ -261,7 +259,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'MILESTONE_DUE_ASC' } + let(:sort_param) { :MILESTONE_DUE_ASC } let(:first_param) { 2 } let(:expected_results) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] } end @@ -269,7 +267,7 @@ RSpec.describe 'getting an issue list for a project' do context 'when descending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'MILESTONE_DUE_DESC' } + let(:sort_param) { :MILESTONE_DUE_DESC } let(:first_param) { 2 } let(:expected_results) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] } end diff --git a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb new file mode 100644 index 00000000000..ac0b18a37d6 --- /dev/null +++ b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.project.mergeRequests.pipelines' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:author) { create(:user) } + let_it_be(:merge_requests) do + %i[with_diffs with_image_diffs conflict].map do |trait| + create(:merge_request, trait, author: author, source_project: project) + end + end + + describe '.count' do + let(:query) do + <<~GQL + query($path: ID!, $first: Int) { + project(fullPath: $path) { + mergeRequests(first: $first) { + nodes { + iid + pipelines { + count + } + } + } + } + } + GQL + end + + def run_query(first = nil) + post_graphql(query, current_user: author, variables: { path: project.full_path, first: first }) + end + + before do + merge_requests.each do |mr| + shas = mr.all_commits.limit(2).pluck(:sha) + + shas.each do |sha| + create(:ci_pipeline, :success, project: project, ref: mr.source_branch, sha: sha) + end + end + end + + it 'produces correct results' do + run_query(2) + + p_nodes = graphql_data_at(:project, :merge_requests, :nodes) + + expect(p_nodes).to all(match('iid' => be_present, 'pipelines' => match('count' => 2))) + end + + it 'is scalable', :request_store, :use_clean_rails_memory_store_caching do + # warm up + run_query + + expect { run_query(2) }.to(issue_same_number_of_queries_as { run_query(1) }.ignoring_cached_queries) + end + end +end diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index 2b8d537f9fc..c05a620bb62 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -259,29 +259,19 @@ RSpec.describe 'getting merge request listings nested in a project' do describe 'sorting and pagination' do let(:data_path) { [:project, :mergeRequests] } - def pagination_query(params, page_info) - graphql_query_for( - :project, - { full_path: project.full_path }, + def pagination_query(params) + graphql_query_for(:project, { full_path: project.full_path }, <<~QUERY mergeRequests(#{params}) { - #{page_info} edges { - node { - id - } - } + #{page_info} nodes { id } } QUERY ) end - def pagination_results_data(data) - data.map { |project| project.dig('node', 'id') } - end - context 'when sorting by merged_at DESC' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { 'MERGED_AT_DESC' } + let(:sort_param) { :MERGED_AT_DESC } let(:first_param) { 2 } let(:expected_results) do @@ -291,7 +281,7 @@ RSpec.describe 'getting merge request listings nested in a project' do merge_request_c, merge_request_e, merge_request_a - ].map(&:to_gid).map(&:to_s) + ].map { |mr| global_id_of(mr) } end before do @@ -304,33 +294,6 @@ RSpec.describe 'getting merge request listings nested in a project' do merge_request_b.metrics.update!(merged_at: 1.day.ago) end - - context 'when paginating backwards' do - let(:params) { 'first: 2, sort: MERGED_AT_DESC' } - let(:page_info) { 'pageInfo { startCursor endCursor }' } - - before do - post_graphql(pagination_query(params, page_info), current_user: current_user) - end - - it 'paginates backwards correctly' do - # first page - first_page_response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges) - end_cursor = graphql_dig_at(Gitlab::Json.parse(response.body), :data, :project, :mergeRequests, :pageInfo, :endCursor) - - # second page - params = "first: 2, after: \"#{end_cursor}\", sort: MERGED_AT_DESC" - post_graphql(pagination_query(params, page_info), current_user: current_user) - start_cursor = graphql_dig_at(Gitlab::Json.parse(response.body), :data, :project, :mergeRequests, :pageInfo, :start_cursor) - - # going back to the first page - - params = "last: 2, before: \"#{start_cursor}\", sort: MERGED_AT_DESC" - post_graphql(pagination_query(params, page_info), current_user: current_user) - backward_paginated_response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges) - expect(first_page_response_data).to eq(backward_paginated_response_data) - end - end end end end diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb index fef0e7e160c..6179b43629b 100644 --- a/spec/requests/api/graphql/project/pipeline_spec.rb +++ b/spec/requests/api/graphql/project/pipeline_spec.rb @@ -5,12 +5,12 @@ require 'spec_helper' RSpec.describe 'getting pipeline information nested in a project' do include GraphqlHelpers - let(:project) { create(:project, :repository, :public) } - let(:pipeline) { create(:ci_pipeline, project: project) } - let(:current_user) { create(:user) } + let!(:project) { create(:project, :repository, :public) } + let!(:pipeline) { create(:ci_pipeline, project: project) } + let!(:current_user) { create(:user) } let(:pipeline_graphql_data) { graphql_data['project']['pipeline'] } - let(:query) do + let!(:query) do graphql_query_for( 'project', { 'fullPath' => project.full_path }, @@ -35,4 +35,45 @@ RSpec.describe 'getting pipeline information nested in a project' do expect(pipeline_graphql_data.dig('configSource')).to eq('UNKNOWN_SOURCE') end + + context 'batching' do + let!(:pipeline2) { create(:ci_pipeline, project: project, user: current_user, builds: [create(:ci_build, :success)]) } + let!(:pipeline3) { create(:ci_pipeline, project: project, user: current_user, builds: [create(:ci_build, :success)]) } + let!(:query) { build_query_to_find_pipeline_shas(pipeline, pipeline2, pipeline3) } + + it 'executes the finder once' do + mock = double(Ci::PipelinesFinder) + opts = { iids: [pipeline.iid, pipeline2.iid, pipeline3.iid].map(&:to_s) } + + expect(Ci::PipelinesFinder).to receive(:new).once.with(project, current_user, opts).and_return(mock) + expect(mock).to receive(:execute).once.and_return(Ci::Pipeline.none) + + post_graphql(query, current_user: current_user) + end + + it 'keeps the queries under the threshold' do + control = ActiveRecord::QueryRecorder.new do + single_pipeline_query = build_query_to_find_pipeline_shas(pipeline) + + post_graphql(single_pipeline_query, current_user: current_user) + end + + aggregate_failures do + expect(response).to have_gitlab_http_status(:success) + expect do + post_graphql(query, current_user: current_user) + end.not_to exceed_query_limit(control) + end + end + end + + private + + def build_query_to_find_pipeline_shas(*pipelines) + pipeline_fields = pipelines.map.each_with_index do |pipeline, idx| + "pipeline#{idx}: pipeline(iid: \"#{pipeline.iid}\") { sha }" + end.join(' ') + + graphql_query_for('project', { 'fullPath' => project.full_path }, pipeline_fields) + end end diff --git a/spec/requests/api/graphql/project/project_members_spec.rb b/spec/requests/api/graphql/project/project_members_spec.rb new file mode 100644 index 00000000000..cb937432ef7 --- /dev/null +++ b/spec/requests/api/graphql/project/project_members_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting project members information' do + include GraphqlHelpers + + let_it_be(:parent_group) { create(:group, :public) } + let_it_be(:parent_project) { create(:project, :public, group: parent_group) } + let_it_be(:user) { create(:user) } + let_it_be(:user_1) { create(:user, username: 'user') } + let_it_be(:user_2) { create(:user, username: 'test') } + + let(:member_data) { graphql_data['project']['projectMembers']['edges'] } + + before_all do + [user_1, user_2].each { |user| parent_group.add_guest(user) } + end + + context 'when the request is correct' do + it_behaves_like 'a working graphql query' do + before_all do + fetch_members(project: parent_project) + end + end + + it 'returns project members successfully' do + fetch_members(project: parent_project) + + expect(graphql_errors).to be_nil + expect_array_response(user_1, user_2) + end + + it 'returns members that match the search query' do + fetch_members(project: parent_project, args: { search: 'test' }) + + expect(graphql_errors).to be_nil + expect_array_response(user_2) + end + end + + context 'member relations' do + let_it_be(:child_group) { create(:group, :public, parent: parent_group) } + let_it_be(:child_project) { create(:project, :public, group: child_group) } + let_it_be(:invited_group) { create(:group, :public) } + let_it_be(:child_user) { create(:user) } + let_it_be(:invited_user) { create(:user) } + let_it_be(:group_link) { create(:project_group_link, project: child_project, group: invited_group) } + + before_all do + child_project.add_guest(child_user) + invited_group.add_guest(invited_user) + end + + it 'returns direct members' do + fetch_members(project: child_project, args: { relations: [:DIRECT] }) + + expect(graphql_errors).to be_nil + expect_array_response(child_user) + end + + it 'returns invited members plus inherited members' do + fetch_members(project: child_project, args: { relations: [:INVITED_GROUPS] }) + + expect(graphql_errors).to be_nil + expect_array_response(invited_user, user_1, user_2) + end + + it 'returns direct, inherited, descendant, and invited members' do + fetch_members(project: child_project, args: { relations: [:DIRECT, :INHERITED, :DESCENDANTS, :INVITED_GROUPS] }) + + expect(graphql_errors).to be_nil + expect_array_response(child_user, user_1, user_2, invited_user) + end + + it 'returns an error for an invalid member relation' do + fetch_members(project: child_project, args: { relations: [:OBLIQUE] }) + + expect(graphql_errors.first) + .to include('path' => %w[query project projectMembers relations], + 'message' => a_string_including('invalid value ([OBLIQUE])')) + end + end + + context 'when unauthenticated' do + it 'returns members' do + fetch_members(current_user: nil, project: parent_project) + + expect(graphql_errors).to be_nil + expect_array_response(user_1, user_2) + end + end + + def fetch_members(project:, current_user: user, args: {}) + post_graphql(members_query(project.full_path, args), current_user: current_user) + end + + def members_query(group_path, args = {}) + members_node = <<~NODE + edges { + node { + user { + id + } + } + } + NODE + + graphql_query_for('project', + { full_path: group_path }, + [query_graphql_field('projectMembers', args, members_node)] + ) + end + + def expect_array_response(*items) + expect(response).to have_gitlab_http_status(:success) + expect(member_data).to be_an Array + expect(member_data.map { |node| node['node']['user']['id'] }) + .to match_array(items.map { |u| global_id_of(u) }) + end +end diff --git a/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb b/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb new file mode 100644 index 00000000000..0f495f3e671 --- /dev/null +++ b/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'rendering project pipeline statistics' do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let(:user) { create(:user) } + + let(:fields) do + <<~QUERY + weekPipelinesTotals + weekPipelinesLabels + monthPipelinesLabels + monthPipelinesTotals + yearPipelinesLabels + yearPipelinesTotals + QUERY + end + + let(:query) do + graphql_query_for('project', + { 'fullPath' => project.full_path }, + query_graphql_field('pipelineAnalytics', {}, fields)) + end + + before do + project.add_maintainer(user) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + it "contains two arrays of 8 elements each for the week pipelines" do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :pipelineAnalytics, :weekPipelinesTotals).length).to eq(8) + expect(graphql_data_at(:project, :pipelineAnalytics, :weekPipelinesLabels).length).to eq(8) + end + + it "contains two arrays of 31 elements each for the month pipelines" do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :pipelineAnalytics, :monthPipelinesTotals).length).to eq(31) + expect(graphql_data_at(:project, :pipelineAnalytics, :monthPipelinesLabels).length).to eq(31) + end + + it "contains two arrays of 13 elements each for the year pipelines" do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :pipelineAnalytics, :yearPipelinesTotals).length).to eq(13) + expect(graphql_data_at(:project, :pipelineAnalytics, :yearPipelinesLabels).length).to eq(13) + end +end diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb index 57dbe258ce4..99b15ff00b1 100644 --- a/spec/requests/api/graphql/project/release_spec.rb +++ b/spec/requests/api/graphql/project/release_spec.rb @@ -36,7 +36,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do let(:path) { path_prefix } let(:release_fields) do - query_graphql_field(%{ + %{ tagName tagPath description @@ -45,7 +45,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do createdAt releasedAt upcomingRelease - }) + } end before do @@ -233,7 +233,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do let(:path) { path_prefix } let(:release_fields) do - query_graphql_field('description') + 'description' end before do @@ -394,10 +394,10 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do let(:current_user) { developer } let(:release_fields) do - query_graphql_field(%{ + %{ releasedAt upcomingRelease - }) + } end before do diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb index 8b67b549efa..2879530acc5 100644 --- a/spec/requests/api/graphql/project/terraform/states_spec.rb +++ b/spec/requests/api/graphql/project/terraform/states_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'query terraform states' do include GraphqlHelpers + include ::API::Helpers::RelatedResourcesHelpers let_it_be(:project) { create(:project) } let_it_be(:terraform_state) { create(:terraform_state, :with_version, :locked, project: project) } @@ -23,6 +24,8 @@ RSpec.describe 'query terraform states' do latestVersion { id + downloadPath + serial createdAt updatedAt @@ -50,22 +53,32 @@ RSpec.describe 'query terraform states' do post_graphql(query, current_user: current_user) end - it 'returns terraform state data', :aggregate_failures do - state = data.dig('nodes', 0) - version = state['latestVersion'] - - expect(state['id']).to eq(terraform_state.to_global_id.to_s) - expect(state['name']).to eq(terraform_state.name) - expect(state['lockedAt']).to eq(terraform_state.locked_at.iso8601) - expect(state['createdAt']).to eq(terraform_state.created_at.iso8601) - expect(state['updatedAt']).to eq(terraform_state.updated_at.iso8601) - expect(state.dig('lockedByUser', 'id')).to eq(terraform_state.locked_by_user.to_global_id.to_s) - - expect(version['id']).to eq(latest_version.to_global_id.to_s) - expect(version['createdAt']).to eq(latest_version.created_at.iso8601) - expect(version['updatedAt']).to eq(latest_version.updated_at.iso8601) - expect(version.dig('createdByUser', 'id')).to eq(latest_version.created_by_user.to_global_id.to_s) - expect(version.dig('job', 'name')).to eq(latest_version.build.name) + it 'returns terraform state data' do + download_path = expose_path( + api_v4_projects_terraform_state_versions_path( + id: project.id, + name: terraform_state.name, + serial: latest_version.version + ) + ) + + expect(data['nodes']).to contain_exactly({ + 'id' => global_id_of(terraform_state), + 'name' => terraform_state.name, + 'lockedAt' => terraform_state.locked_at.iso8601, + 'createdAt' => terraform_state.created_at.iso8601, + 'updatedAt' => terraform_state.updated_at.iso8601, + 'lockedByUser' => { 'id' => global_id_of(terraform_state.locked_by_user) }, + 'latestVersion' => { + 'id' => eq(global_id_of(latest_version)), + 'serial' => eq(latest_version.version), + 'downloadPath' => eq(download_path), + 'createdAt' => eq(latest_version.created_at.iso8601), + 'updatedAt' => eq(latest_version.updated_at.iso8601), + 'createdByUser' => { 'id' => eq(global_id_of(latest_version.created_by_user)) }, + 'job' => { 'name' => eq(latest_version.build.name) } + } + }) end it 'returns count of terraform states' do |