diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-12 06:10:17 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-12 06:10:17 +0300 |
commit | 737684a392db1178770ad5b1d20b64386aadcac5 (patch) | |
tree | a5b304cf7cd4d0c41ad3bde432d20edd7d79257d /spec | |
parent | 80ddaef34dd357706187bd888b34e7ca1d5c30ba (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/frontend/blob/components/blob_edit_content_spec.js | 44 | ||||
-rw-r--r-- | spec/frontend/blob/utils_spec.js | 47 | ||||
-rw-r--r-- | spec/frontend/editor/editor_lite_spec.js | 28 | ||||
-rw-r--r-- | spec/frontend/labels_select_spec.js | 31 | ||||
-rw-r--r-- | spec/frontend/snippets/components/__snapshots__/snippet_blob_edit_spec.js.snap | 1 | ||||
-rw-r--r-- | spec/frontend/snippets/components/snippet_blob_edit_spec.js | 10 | ||||
-rw-r--r-- | spec/graphql/resolvers/issue_status_counts_resolver_spec.rb | 64 | ||||
-rw-r--r-- | spec/graphql/types/issue_status_count_type_spec.rb | 17 | ||||
-rw-r--r-- | spec/graphql/types/project_type_spec.rb | 1 | ||||
-rw-r--r-- | spec/lib/gitlab/issuables_count_for_state_spec.rb | 15 | ||||
-rw-r--r-- | spec/requests/api/graphql/issue_status_counts_spec.rb | 58 | ||||
-rw-r--r-- | spec/requests/api/internal/kubernetes_spec.rb | 46 | ||||
-rw-r--r-- | spec/requests/api/project_templates_spec.rb | 52 |
13 files changed, 321 insertions, 93 deletions
diff --git a/spec/frontend/blob/components/blob_edit_content_spec.js b/spec/frontend/blob/components/blob_edit_content_spec.js index 971ef72521d..914b7eb874e 100644 --- a/spec/frontend/blob/components/blob_edit_content_spec.js +++ b/spec/frontend/blob/components/blob_edit_content_spec.js @@ -1,28 +1,31 @@ import { shallowMount } from '@vue/test-utils'; import BlobEditContent from '~/blob/components/blob_edit_content.vue'; -import { initEditorLite } from '~/blob/utils'; +import * as utils from '~/blob/utils'; +import Editor from '~/editor/editor_lite'; import { nextTick } from 'vue'; -jest.mock('~/blob/utils', () => ({ - initEditorLite: jest.fn(), -})); +jest.mock('~/editor/editor_lite'); describe('Blob Header Editing', () => { let wrapper; const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; const fileName = 'lorem.txt'; + const fileGlobalId = 'snippet_777'; function createComponent(props = {}) { wrapper = shallowMount(BlobEditContent, { propsData: { value, fileName, + fileGlobalId, ...props, }, }); } beforeEach(() => { + jest.spyOn(utils, 'initEditorLite'); + createComponent(); }); @@ -30,6 +33,15 @@ describe('Blob Header Editing', () => { wrapper.destroy(); }); + const triggerChangeContent = val => { + jest.spyOn(Editor.prototype, 'getValue').mockReturnValue(val); + const [cb] = Editor.prototype.onChangeContent.mock.calls[0]; + + cb(); + + jest.runOnlyPendingTimers(); + }; + describe('rendering', () => { it('matches the snapshot', () => { expect(wrapper.element).toMatchSnapshot(); @@ -51,18 +63,15 @@ describe('Blob Header Editing', () => { it('initialises Editor Lite', () => { const el = wrapper.find({ ref: 'editor' }).element; - expect(initEditorLite).toHaveBeenCalledWith({ + expect(utils.initEditorLite).toHaveBeenCalledWith({ el, blobPath: fileName, + blobGlobalId: fileGlobalId, blobContent: value, }); }); it('reacts to the changes in fileName', () => { - wrapper.vm.editor = { - updateModelLanguage: jest.fn(), - }; - const newFileName = 'ipsum.txt'; wrapper.setProps({ @@ -70,21 +79,20 @@ describe('Blob Header Editing', () => { }); return nextTick().then(() => { - expect(wrapper.vm.editor.updateModelLanguage).toHaveBeenCalledWith(newFileName); + expect(Editor.prototype.updateModelLanguage).toHaveBeenCalledWith(newFileName); }); }); + it('registers callback with editor onChangeContent', () => { + expect(Editor.prototype.onChangeContent).toHaveBeenCalledWith(expect.any(Function)); + }); + it('emits input event when the blob content is changed', () => { - const editorEl = wrapper.find({ ref: 'editor' }); - wrapper.vm.editor = { - getValue: jest.fn().mockReturnValue(value), - }; + expect(wrapper.emitted().input).toBeUndefined(); - editorEl.trigger('keyup'); + triggerChangeContent(value); - return nextTick().then(() => { - expect(wrapper.emitted().input[0]).toEqual([value]); - }); + expect(wrapper.emitted().input).toEqual([[value]]); }); }); }); diff --git a/spec/frontend/blob/utils_spec.js b/spec/frontend/blob/utils_spec.js index 119ed2dfe7a..62e5d57dba2 100644 --- a/spec/frontend/blob/utils_spec.js +++ b/spec/frontend/blob/utils_spec.js @@ -1,26 +1,17 @@ import Editor from '~/editor/editor_lite'; import * as utils from '~/blob/utils'; -const mockCreateMonacoInstance = jest.fn(); -jest.mock('~/editor/editor_lite', () => { - return jest.fn().mockImplementation(() => { - return { createInstance: mockCreateMonacoInstance }; - }); -}); +jest.mock('~/editor/editor_lite'); describe('Blob utilities', () => { - beforeEach(() => { - Editor.mockClear(); - }); - describe('initEditorLite', () => { let editorEl; const blobPath = 'foo.txt'; const blobContent = 'Foo bar'; + const blobGlobalId = 'snippet_777'; beforeEach(() => { - setFixtures('<div id="editor"></div>'); - editorEl = document.getElementById('editor'); + editorEl = document.createElement('div'); }); describe('Monaco editor', () => { @@ -29,25 +20,21 @@ describe('Blob utilities', () => { expect(Editor).toHaveBeenCalled(); }); - it('creates the instance with the passed parameters', () => { - utils.initEditorLite({ el: editorEl }); - expect(mockCreateMonacoInstance.mock.calls[0]).toEqual([ - { + it.each([[{}], [{ blobPath, blobContent, blobGlobalId }]])( + 'creates the instance with the passed parameters %s', + extraParams => { + const params = { el: editorEl, - blobPath: undefined, - blobContent: undefined, - }, - ]); - - utils.initEditorLite({ el: editorEl, blobPath, blobContent }); - expect(mockCreateMonacoInstance.mock.calls[1]).toEqual([ - { - el: editorEl, - blobPath, - blobContent, - }, - ]); - }); + ...extraParams, + }; + + expect(Editor.prototype.createInstance).not.toHaveBeenCalled(); + + utils.initEditorLite(params); + + expect(Editor.prototype.createInstance).toHaveBeenCalledWith(params); + }, + ); }); }); }); diff --git a/spec/frontend/editor/editor_lite_spec.js b/spec/frontend/editor/editor_lite_spec.js index 92a136835bf..e4edeab172b 100644 --- a/spec/frontend/editor/editor_lite_spec.js +++ b/spec/frontend/editor/editor_lite_spec.js @@ -2,13 +2,15 @@ import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monac import Editor from '~/editor/editor_lite'; import { DEFAULT_THEME, themes } from '~/ide/lib/themes'; +const URI_PREFIX = 'gitlab'; + describe('Base editor', () => { let editorEl; let editor; const blobContent = 'Foo Bar'; const blobPath = 'test.md'; - const uri = new Uri('gitlab', false, blobPath); - const fakeModel = { foo: 'bar' }; + const blobGlobalId = 'snippet_777'; + const fakeModel = { foo: 'bar', dispose: jest.fn() }; beforeEach(() => { setFixtures('<div id="editor" data-editor-loading></div>'); @@ -21,6 +23,8 @@ describe('Base editor', () => { editorEl.remove(); }); + const createUri = (...paths) => Uri.file([URI_PREFIX, ...paths].join('/')); + it('initializes Editor with basic properties', () => { expect(editor).toBeDefined(); expect(editor.editorEl).toBe(null); @@ -65,7 +69,7 @@ describe('Base editor', () => { it('creates model to be supplied to Monaco editor', () => { editor.createInstance({ el: editorEl, blobPath, blobContent }); - expect(modelSpy).toHaveBeenCalledWith(blobContent, undefined, uri); + expect(modelSpy).toHaveBeenCalledWith(blobContent, undefined, createUri(blobPath)); expect(setModel).toHaveBeenCalledWith(fakeModel); }); @@ -75,6 +79,16 @@ describe('Base editor', () => { expect(editor.editorEl).not.toBe(null); expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.anything()); }); + + it('with blobGlobalId, creates model with id in uri', () => { + editor.createInstance({ el: editorEl, blobPath, blobContent, blobGlobalId }); + + expect(modelSpy).toHaveBeenCalledWith( + blobContent, + undefined, + createUri(blobGlobalId, blobPath), + ); + }); }); describe('implementation', () => { @@ -82,10 +96,6 @@ describe('Base editor', () => { editor.createInstance({ el: editorEl, blobPath, blobContent }); }); - afterEach(() => { - editor.model.dispose(); - }); - it('correctly proxies value from the model', () => { expect(editor.getValue()).toEqual(blobContent); }); @@ -132,10 +142,6 @@ describe('Base editor', () => { editor.createInstance({ el: editorEl, blobPath, blobContent }); }); - afterEach(() => { - editor.model.dispose(); - }); - it('is extensible with the extensions', () => { expect(editor.foo).toBeUndefined(); diff --git a/spec/frontend/labels_select_spec.js b/spec/frontend/labels_select_spec.js index 8b08eb9e124..cbc9a923f8b 100644 --- a/spec/frontend/labels_select_spec.js +++ b/spec/frontend/labels_select_spec.js @@ -29,7 +29,7 @@ const mockScopedLabels2 = [ title: 'Foo::Bar2', description: 'Foobar2', color: '#FFFFFF', - text_color: '#000000', + text_color: '#333333', }, ]; @@ -61,10 +61,11 @@ describe('LabelsSelect', () => { expect($labelEl.find('a').attr('title')).toBe(label.description); }); - it('generated label item template has correct label styles', () => { + it('generated label item template has correct label styles and classes', () => { expect($labelEl.find('span.gl-label-text').attr('style')).toBe( - `background-color: ${label.color}; color: ${label.text_color};`, + `background-color: ${label.color};`, ); + expect($labelEl.find('span.gl-label-text')).toHaveClass('gl-label-text-light'); }); it('generated label item has a gl-label-text class', () => { @@ -100,16 +101,12 @@ describe('LabelsSelect', () => { expect($labelEl.find('a').attr('data-html')).toBe('true'); }); - it('generated label item template has correct label styles', () => { + it('generated label item template has correct label styles and classes', () => { expect($labelEl.find('span.gl-label-text').attr('style')).toBe( - `background-color: ${label.color}; color: ${label.text_color};`, + `background-color: ${label.color};`, ); - expect( - $labelEl - .find('span.gl-label-text') - .last() - .attr('style'), - ).toBe(`color: ${label.color};`); + expect($labelEl.find('span.gl-label-text')).toHaveClass('gl-label-text-light'); + expect($labelEl.find('span.gl-label-text').last()).not.toHaveClass('gl-label-text-light'); }); it('generated label item has a badge class', () => { @@ -131,16 +128,12 @@ describe('LabelsSelect', () => { ); }); - it('generated label item template has correct label styles', () => { + it('generated label item template has correct label styles and classes', () => { expect($labelEl.find('span.gl-label-text').attr('style')).toBe( - `background-color: ${label.color}; color: ${label.text_color};`, + `background-color: ${label.color};`, ); - expect( - $labelEl - .find('span.gl-label-text') - .last() - .attr('style'), - ).toBe(`color: ${label.text_color};`); + expect($labelEl.find('span.gl-label-text')).toHaveClass('gl-label-text-dark'); + expect($labelEl.find('span.gl-label-text').last()).toHaveClass('gl-label-text-dark'); }); }); }); diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_blob_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_blob_edit_spec.js.snap index 959bc24eef6..f60ffd00792 100644 --- a/spec/frontend/snippets/components/__snapshots__/snippet_blob_edit_spec.js.snap +++ b/spec/frontend/snippets/components/__snapshots__/snippet_blob_edit_spec.js.snap @@ -17,6 +17,7 @@ exports[`Snippet Blob Edit component rendering matches the snapshot 1`] = ` /> <blob-content-edit-stub + fileglobalid="0a3d" filename="lorem.txt" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit." /> diff --git a/spec/frontend/snippets/components/snippet_blob_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_edit_spec.js index 009074b4558..5f38fe9d56a 100644 --- a/spec/frontend/snippets/components/snippet_blob_edit_spec.js +++ b/spec/frontend/snippets/components/snippet_blob_edit_spec.js @@ -51,6 +51,10 @@ describe('Snippet Blob Edit component', () => { } beforeEach(() => { + // This component generates a random id. Soon this will be abstracted away, but for now let's make this deterministic. + // see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38855 + jest.spyOn(Math, 'random').mockReturnValue(0.04); + axiosMock = new AxiosMockAdapter(axios); createComponent(); }); @@ -68,7 +72,11 @@ describe('Snippet Blob Edit component', () => { it('renders required components', () => { expect(findComponent(BlobHeaderEdit).exists()).toBe(true); - expect(findComponent(BlobContentEdit).exists()).toBe(true); + expect(findComponent(BlobContentEdit).props()).toEqual({ + fileGlobalId: expect.any(String), + fileName: '', + value: '', + }); }); it('renders loader if existing blob is supplied but no content is fetched yet', () => { diff --git a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb new file mode 100644 index 00000000000..d2412db35c6 --- /dev/null +++ b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::IssueStatusCountsResolver do + include GraphqlHelpers + + describe '#resolve' do + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:issue) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) } + let_it_be(:incident) { create(:incident, project: project, state: :closed, created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) } + + let(:args) { {} } + + before do + project.add_developer(current_user) + end + + subject { resolve_issue_status_counts(args) } + + it { is_expected.to be_a(Gitlab::IssuablesCountForState) } + specify { expect(subject.project).to eq(project) } + + it 'returns expected results' do + result = resolve_issue_status_counts + + expect(result.all).to eq 2 + expect(result.opened).to eq 1 + expect(result.closed).to eq 1 + end + + it 'filters by search', :aggregate_failures do + result = resolve_issue_status_counts(search: issue.title) + + expect(result.all).to eq 1 + expect(result.opened).to eq 1 + expect(result.closed).to eq 0 + end + + it 'filters by issue type', :aggregate_failures do + result = resolve_issue_status_counts(issue_types: ['incident']) + + expect(result.all).to eq 1 + expect(result.opened).to eq 0 + expect(result.closed).to eq 1 + end + + # The state param is ignored in IssuableFinder#count_by_state + it 'ignores state filter', :aggregate_failures do + result = resolve_issue_status_counts(state: 'closed') + + expect(result.all).to eq 2 + expect(result.opened).to eq 1 + expect(result.closed).to eq 1 + end + + private + + def resolve_issue_status_counts(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: project, args: args, ctx: context) + end + end +end diff --git a/spec/graphql/types/issue_status_count_type_spec.rb b/spec/graphql/types/issue_status_count_type_spec.rb new file mode 100644 index 00000000000..4e273d6415a --- /dev/null +++ b/spec/graphql/types/issue_status_count_type_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['IssueStatusCountsType'] do + specify { expect(described_class.graphql_name).to eq('IssueStatusCountsType') } + + it 'exposes the expected fields' do + expected_fields = %i[ + all + opened + closed + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 3fcd8f05c86..5be1fafffb6 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -27,6 +27,7 @@ RSpec.describe GitlabSchema.types['Project'] do boards jira_import_status jira_imports services releases release alert_management_alerts alert_management_alert alert_management_alert_status_counts container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address + issue_status_counts ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/lib/gitlab/issuables_count_for_state_spec.rb b/spec/lib/gitlab/issuables_count_for_state_spec.rb index 1c186a8e6ca..d96152e47ea 100644 --- a/spec/lib/gitlab/issuables_count_for_state_spec.rb +++ b/spec/lib/gitlab/issuables_count_for_state_spec.rb @@ -9,6 +9,21 @@ RSpec.describe Gitlab::IssuablesCountForState do let(:counter) { described_class.new(finder) } + describe 'project given' do + let(:project) { build(:project) } + let(:counter) { described_class.new(finder, project) } + + it 'provides the project' do + expect(counter.project).to eq(project) + end + end + + describe '.declarative_policy_class' do + subject { described_class.declarative_policy_class } + + it { is_expected.to eq('IssuablePolicy') } + end + describe '#for_state_or_opened' do it 'returns the number of issuables for the given state' do expect(counter.for_state_or_opened(:closed)).to eq(1) diff --git a/spec/requests/api/graphql/issue_status_counts_spec.rb b/spec/requests/api/graphql/issue_status_counts_spec.rb new file mode 100644 index 00000000000..3d8817c3bc5 --- /dev/null +++ b/spec/requests/api/graphql/issue_status_counts_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'getting Issue counts by status' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:current_user) { create(:user) } + let_it_be(:issue_opened) { create(:issue, project: project) } + let_it_be(:issue_closed) { create(:issue, :closed, project: project) } + let_it_be(:other_project_issue) { create(:issue) } + let(:params) { {} } + + let(:fields) do + <<~QUERY + #{all_graphql_fields_for('IssueStatusCountsType'.classify)} + QUERY + end + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('issueStatusCounts', params, fields) + ) + end + + context 'with issue count data' do + let(:issue_counts) { graphql_data.dig('project', 'issueStatusCounts') } + + context 'without project permissions' do + let(:user) { create(:user) } + + before do + post_graphql(query, current_user: current_user) + end + + it_behaves_like 'a working graphql query' + it { expect(issue_counts).to be nil } + end + + context 'with project permissions' do + before do + project.add_developer(current_user) + post_graphql(query, current_user: current_user) + end + + it_behaves_like 'a working graphql query' + it 'returns the correct counts for each status' do + expect(issue_counts).to eq( + 'all' => 2, + 'opened' => 1, + 'closed' => 1 + ) + end + end + end +end diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index 4c7e83abf19..555ca441fe7 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -33,13 +33,24 @@ RSpec.describe API::Internal::Kubernetes do expect(response).to have_gitlab_http_status(:success) - expect(json_response['project_id']).to eq(project.id) - expect(json_response['agent_id']).to eq(agent.id) - expect(json_response['agent_name']).to eq(agent.name) - expect(json_response['storage_name']).to eq(project.repository_storage) - expect(json_response['relative_path']).to eq(project.disk_path + '.git') - expect(json_response['gl_repository']).to eq("project-#{project.id}") - expect(json_response['gl_project_path']).to eq(project.full_path) + expect(json_response).to match( + a_hash_including( + 'project_id' => project.id, + 'agent_id' => agent.id, + 'agent_name' => agent.name, + 'gitaly_info' => a_hash_including( + 'address' => match(/\.socket$/), + 'token' => 'secret', + 'features' => {} + ), + 'gitaly_repository' => a_hash_including( + 'storage_name' => project.repository_storage, + 'relative_path' => project.disk_path + '.git', + 'gl_repository' => "project-#{project.id}", + 'gl_project_path' => project.full_path + ) + ) + ) end end @@ -92,11 +103,22 @@ RSpec.describe API::Internal::Kubernetes do expect(response).to have_gitlab_http_status(:success) - expect(json_response['project_id']).to eq(project.id) - expect(json_response['storage_name']).to eq(project.repository_storage) - expect(json_response['relative_path']).to eq(project.disk_path + '.git') - expect(json_response['gl_repository']).to eq("project-#{project.id}") - expect(json_response['gl_project_path']).to eq(project.full_path) + expect(json_response).to match( + a_hash_including( + 'project_id' => project.id, + 'gitaly_info' => a_hash_including( + 'address' => match(/\.socket$/), + 'token' => 'secret', + 'features' => {} + ), + 'gitaly_repository' => a_hash_including( + 'storage_name' => project.repository_storage, + 'relative_path' => project.disk_path + '.git', + 'gl_repository' => "project-#{project.id}", + 'gl_project_path' => project.full_path + ) + ) + ) end end diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb index 51123837a50..d242d49fc1b 100644 --- a/spec/requests/api/project_templates_spec.rb +++ b/spec/requests/api/project_templates_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe API::ProjectTemplates do - let_it_be(:public_project) { create(:project, :public, path: 'path.with.dot') } - let_it_be(:private_project) { create(:project, :private) } + let_it_be(:public_project) { create(:project, :public, :repository, create_templates: :merge_request, path: 'path.with.dot') } + let_it_be(:private_project) { create(:project, :private, :repository, create_templates: :issue) } let_it_be(:developer) { create(:user) } let(:url_encoded_path) { "#{public_project.namespace.path}%2F#{public_project.path}" } @@ -71,6 +71,24 @@ RSpec.describe API::ProjectTemplates do expect(json_response).to satisfy_one { |template| template['key'] == 'Default' } end + it 'returns issue templates' do + get api("/projects/#{private_project.id}/templates/issues", developer) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(response).to match_response_schema('public_api/v4/template_list') + expect(json_response.map {|t| t['key']}).to match_array(%w(bug feature_proposal template_test)) + end + + it 'returns merge request templates' do + get api("/projects/#{public_project.id}/templates/merge_requests") + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(response).to match_response_schema('public_api/v4/template_list') + expect(json_response.map {|t| t['key']}).to match_array(%w(bug feature_proposal template_test)) + end + it 'returns 400 for an unknown template type' do get api("/projects/#{public_project.id}/templates/unknown") @@ -160,12 +178,42 @@ RSpec.describe API::ProjectTemplates do expect(response).to match_response_schema('public_api/v4/license') end + it 'returns a specific issue template' do + get api("/projects/#{private_project.id}/templates/issues/bug", developer) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('bug') + expect(json_response['content']).to eq('something valid') + end + + it 'returns a specific merge request template' do + get api("/projects/#{public_project.id}/templates/merge_requests/feature_proposal") + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('feature_proposal') + expect(json_response['content']).to eq('feature_proposal') # Content is identical to filename here + end + it 'returns 404 for an unknown specific template' do get api("/projects/#{public_project.id}/templates/licenses/unknown") expect(response).to have_gitlab_http_status(:not_found) end + it 'returns 404 for an unknown issue template' do + get api("/projects/#{public_project.id}/templates/issues/unknown") + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'returns 404 for an unknown merge request template' do + get api("/projects/#{public_project.id}/templates/merge_requests/unknown") + + expect(response).to have_gitlab_http_status(:not_found) + end + it 'denies access to an anonymous user on a private project' do get api("/projects/#{private_project.id}/templates/licenses/mit") |