Commit a6bddf15 authored by Daniel Barker's avatar Daniel Barker Committed by Robert Speicher

Add instance-level license template

parent 087673ac
import initSettingsPanels from '~/settings_panels';
import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
projectSelect();
});
......@@ -14,6 +14,7 @@ export default function projectSelect() {
this.orderBy = $(select).data('orderBy') || 'id';
this.withIssuesEnabled = $(select).data('withIssuesEnabled');
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
this.allowClear = $(select).data('allowClear') || false;
placeholder = "Search for project";
if (this.includeGroups) {
......@@ -71,6 +72,13 @@ export default function projectSelect() {
text: function (project) {
return project.name_with_namespace || project.name;
},
initSelection: function(el, callback) {
return Api.project(el.val()).then(({ data }) => callback(data));
},
allowClear: this.allowClear,
dropdownCssClass: "ajax-project-dropdown"
});
if (simpleFilter) return select;
......
# LicenseTemplateFinder
#
# Used to find license templates, which may come from a variety of external
# sources
#
# Arguments:
# popular: boolean. When set to true, only "popular" licenses are shown. When
# false, all licenses except popular ones are shown. When nil (the
# default), *all* licenses will be shown.
class LicenseTemplateFinder
prepend ::EE::LicenseTemplateFinder
attr_reader :params
def initialize(params = {})
@params = params
end
def execute
Licensee::License.all(featured: popular_only?).map do |license|
LicenseTemplate.new(
id: license.key,
name: license.name,
nickname: license.nickname,
category: (license.featured? ? :Popular : :Other),
content: license.content,
url: license.url,
meta: license.meta
)
end
end
private
def popular_only?
params.fetch(:popular, nil)
end
end
......@@ -182,12 +182,14 @@ module BlobHelper
def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select)
licenses = Licensee::License.all
grouped_licenses = LicenseTemplateFinder.new.execute.group_by(&:category)
categories = grouped_licenses.keys
@licenses_for_select = {
Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } },
Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } }
}
@licenses_for_select = categories.each_with_object({}) do |category, hash|
hash[category] = grouped_licenses[category].map do |license|
{ name: license.name, id: license.id }
end
end
end
def ref_project
......
class LicenseTemplate
PROJECT_TEMPLATE_REGEX =
%r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
[\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
%r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]}xi.freeze
attr_reader :id, :name, :category, :nickname, :url, :meta
alias_method :key, :id
def initialize(id:, name:, category:, content:, nickname: nil, url: nil, meta: {})
@id = id
@name = name
@category = category
@content = content
@nickname = nickname
@url = url
@meta = meta
end
def popular?
category == :Popular
end
alias_method :featured?, :popular?
# Returns the text of the license
def content
if @content.respond_to?(:call)
@content = @content.call
else
@content
end
end
# Populate placeholders in the LicenseTemplate content
def resolve!(project_name: nil, fullname: nil, year: Time.now.year.to_s)
# Ensure the string isn't shared with any other instance of LicenseTemplate
new_content = content.dup
new_content.gsub!(YEAR_TEMPLATE_REGEX, year) if year.present?
new_content.gsub!(PROJECT_TEMPLATE_REGEX, project_name) if project_name.present?
new_content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname.present?
@content = new_content
self
end
end
......@@ -385,6 +385,8 @@
.settings-content
= render partial: 'slack'
= render_if_exists 'admin/application_settings/templates', expanded: expanded
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
......
......@@ -206,6 +206,7 @@ ActiveRecord::Schema.define(version: 20180807153545) do
t.string "encrypted_external_auth_client_key_pass_iv"
t.string "email_additional_text"
t.boolean "enforce_terms", default: false
t.integer "file_template_project_id"
t.boolean "pseudonymizer_enabled", default: false, null: false
t.boolean "hide_third_party_offers", default: false, null: false
t.boolean "snowplow_enabled", default: false, null: false
......@@ -2962,6 +2963,7 @@ ActiveRecord::Schema.define(version: 20180807153545) do
add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree
add_foreign_key "application_settings", "namespaces", column: "custom_project_templates_group_id", on_delete: :nullify
add_foreign_key "application_settings", "projects", column: "file_template_project_id", name: "fk_ec757bd087", on_delete: :nullify
add_foreign_key "approvals", "merge_requests", name: "fk_310d714958", on_delete: :cascade
add_foreign_key "approver_groups", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "badges", "namespaces", column: "group_id", on_delete: :cascade
......
......@@ -56,7 +56,8 @@ Example response:
"enforce_terms": true,
"terms": "Hello world!",
"performance_bar_allowed_group_id": 42,
"instance_statistics_visibility_private": false
"instance_statistics_visibility_private": false,
"file_template_project_id": 1
}
```
......@@ -107,6 +108,7 @@ PUT /application/settings
| `email_author_in_body` | boolean | no | Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. |
| `email_additional_text` | string | no | **(Premium)** Additional text added to the bottom of every email for legal/auditing/compliance reasons reasons |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `file_template_project_id | integer | no | **(Premium)** The ID of a project to load custom file templates from |
| `geo_status_timeout` | integer | no | The amount of seconds after which a request to get a secondary node status will time out. |
| `gravatar_enabled` | boolean | no | Enable Gravatar |
| `help_page_hide_commercial_content` | boolean | no | Hide marketing-related entries from help |
......@@ -236,6 +238,7 @@ Example response:
"enforce_terms": true,
"terms": "Hello world!",
"performance_bar_allowed_group_id": 42,
"instance_statistics_visibility_private": false
"instance_statistics_visibility_private": false,
"file_template_project_id": 1
}
```
# Instance-level Template Repository
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5986) in
> [GitLab Premium](https://about.gitlab.com/pricing) 11.3.
## Overview
In hosted systems, enterprises often have a need to share their own templates
across teams. This feature allows an administrator to pick a project to be the
instance-wide collection of templates. These templates are then exposed to all
users while the project remains secure. Currently supported templates: Licenses.
## Configuration
An administrator can choose any project to be the template repository. This is
done through the `Settings` page in the `Admin Area` or through the API. On the
`Settings` page, there is a `Templates` section with a selection box for
choosing a project:
![](img/file_template_admin_area.png)
Once a project has been selected you can add custom templates to the repository,
and they will appear in the appropriate places in the frontend and API.
Templates must be added to a specific subdirectory in the repository,
corresponding to the kind of template. They must also have the correct extension
for the template type.
Currently, only custom license templates are supported. This must go in the
`LICENSE/` subdirectory, and must have `.txt` file extensions. So, the hierarchy
should look like this:
```text
|-- README.md
|-- LICENSE
|-- custom_license.txt
|-- another_license.txt
```
Once this is established, the list of `Custom` licenses will be included when
creating a new file and the file type is `License`. These will appear at the
bottom of the list:
![](img/file_template_user_dropdown.png)
If this feature has been disabled or no licenses are present, then there will be
no `Custom` section in the selection dropdown.
......@@ -24,6 +24,10 @@ module EE
attrs << :email_additional_text
end
if License.feature_available?(:custom_file_templates)
attrs << :file_template_project_id
end
if License.feature_available?(:pseudonymizer)
attrs << :pseudonymizer_enabled
end
......
module EE
module LicenseTemplateFinder
include ::Gitlab::Utils::StrongMemoize
extend ::Gitlab::Utils::Override
override :execute
def execute
return super unless custom_templates?
extra = custom_licenses.map do |template|
LicenseTemplate.new(
id: template.name,
name: template.name,
nickname: template.name,
category: :Custom,
content: -> { template.content }
)
end
super + extra
end
private
def custom_templates?
!popular_only? &&
::License.feature_available?(:custom_file_templates) &&
template_project.present?
end
def custom_licenses
::Gitlab::Template::LicenseTemplate.all(template_project)
end
def template_project
strong_memoize(:template_project) { ::Gitlab::CurrentSettings.file_template_project }
end
end
end
......@@ -97,7 +97,10 @@ module EE
end
def self.possible_licensed_attributes
repository_mirror_attributes + external_authorization_service_attributes + [:email_additional_text]
repository_mirror_attributes + external_authorization_service_attributes + %i[
email_additional_text
file_template_project_id
]
end
end
end
......@@ -11,6 +11,8 @@ module EE
EMAIL_ADDITIONAL_TEXT_CHARACTER_LIMIT = 10_000
belongs_to :file_template_project, class_name: "Project"
ignore_column :minimum_mirror_sync_time
validates :shared_runners_minutes,
......
......@@ -40,6 +40,7 @@ class License < ActiveRecord::Base
board_assignee_lists
board_milestone_lists
cross_project_pipelines
custom_file_templates
email_additional_text
db_load_balancing
deploy_board
......@@ -150,6 +151,7 @@ class License < ActiveRecord::Base
GLOBAL_FEATURES = %i[
admin_audit_log
auditor_user
custom_file_templates
db_load_balancing
elastic_search
extended_audit_events
......
- if License.feature_available?(:custom_file_templates)
%section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _('Templates')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Set instance-wide template repository')
.settings-content
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :file_template_project_id, class: 'label-light' do
.form-text.text-muted
Select a
= link_to 'template repository', help_page_path("user/admin_area/settings/instance_template_repository", anchor: "version-check")
= project_select_tag('application_setting[file_template_project_id]', class: 'project-item-select hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
placeholder: admin_project_dropdown_label('Search projects'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', all_projects: 'true', simple_filter: true, allow_clear: true}, value: @application_setting.file_template_project_id)
= f.submit 'Save changes', class: "btn btn-success"
---
title: Added an instance-level license template project
merge_request: 6631
author: Dan Barker
type: added
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddProjectToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column :application_settings, :file_template_project_id, :integer
add_concurrent_foreign_key :application_settings, :projects, column: :file_template_project_id, on_delete: :nullify
end
def down
remove_foreign_key :application_settings, column: :file_template_project_id
remove_column :application_settings, :file_template_project_id, :integer
end
end
......@@ -120,6 +120,7 @@ module EE
::License.feature_available?(:external_authorization_service)
end)
expose :email_additional_text, if: ->(_instance, _opts) { ::License.feature_available?(:email_additional_text) }
expose :file_template_project_id, if: ->(_instance, _opts) { ::License.feature_available?(:custom_file_templates) }
end
end
......
module Gitlab
module Template
class LicenseTemplate < BaseTemplate
class << self
def extension
'.txt'
end
def base_dir
'LICENSE/'
end
def finder(project)
Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories)
end
end
end
end
end
require 'spec_helper'
describe LicenseTemplateFinder do
describe '#execute' do
subject(:result) { described_class.new(params).execute }
let(:params) { {} }
let(:project) { create(:project) }
let(:custom) { result.select { |template| template.category == :Custom } }
before do
stub_ee_application_setting(file_template_project: project)
allow(Gitlab::Template::LicenseTemplate)
.to receive(:all)
.with(project)
.and_return([OpenStruct.new(name: "custom template")])
end
context 'custom file templates feature enabled' do
before do
stub_licensed_features(custom_file_templates: true)
end
it 'includes custom file templates' do
expect(custom.map(&:name)).to contain_exactly("custom template")
end
it 'skips custom file templates when only "popular" templates are requested' do
params[:popular] = true
expect(custom).to be_empty
end
end
context 'custom file templates feature disabled' do
it 'does not include custom file templates' do
stub_licensed_features(custom_file_templates: false)
expect(custom).to be_empty
end
end
end
end
require 'spec_helper'
describe BlobHelper do
include TreeHelper
describe '#licenses_for_select' do
subject(:result) { helper.licenses_for_select }
let(:categories) { result.keys }
let(:custom) { result[:Custom] }
let(:popular) { result[:Popular] }
let(:other) { result[:Other] }
let(:project) { create(:project) }
it 'returns Custom licenses when enabled' do
stub_licensed_features(custom_file_templates: true)
stub_ee_application_setting(file_template_project: project)
expect(Gitlab::Template::LicenseTemplate)
.to receive(:all)
.with(project)
.and_return([OpenStruct.new(name: "name")])
expect(categories).to contain_exactly(:Popular, :Other, :Custom)
expect(custom).to contain_exactly({ name: "name", id: "name" })
expect(popular).to be_present
expect(other).to be_present
end
it 'returns no Custom licenses when disabled' do
stub_licensed_features(custom_file_templates: false)
expect(categories).to contain_exactly(:Popular, :Other)
expect(custom).to be_nil
expect(popular).to be_present
expect(other).to be_present
end
end
end
......@@ -5,6 +5,7 @@ describe API::Settings, 'EE Settings' do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
......@@ -12,12 +13,15 @@ describe API::Settings, 'EE Settings' do
describe "PUT /application/settings" do
it 'sets EE specific settings' do
stub_licensed_features(custom_file_templates: true)
put api("/application/settings", admin),
help_text: 'Help text',
snowplow_collector_uri: 'https://snowplow.example.com',
snowplow_cookie_domain: '.example.com',
snowplow_enabled: true,
snowplow_site_id: 'site_id'
snowplow_site_id: 'site_id',
file_template_project_id: project.id
expect(response).to have_gitlab_http_status(200)
expect(json_response['help_text']).to eq('Help text')
......@@ -25,6 +29,7 @@ describe API::Settings, 'EE Settings' do
expect(json_response['snowplow_cookie_domain']).to eq('.example.com')
expect(json_response['snowplow_enabled']).to be_truthy
expect(json_response['snowplow_site_id']).to eq('site_id')
expect(json_response['file_template_project_id']).to eq(project.id)
end
end
......@@ -72,6 +77,7 @@ describe API::Settings, 'EE Settings' do
it 'allows updating the settings' do
put api("/application/settings", admin), settings
expect(response).to have_gitlab_http_status(200)
settings.each do |attribute, value|
expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
......@@ -111,6 +117,13 @@ describe API::Settings, 'EE Settings' do
it_behaves_like 'settings for licensed features'
end
context 'custom file template project' do
let(:settings) { { file_template_project_id: project.id } }
let(:feature) { :custom_file_templates }
it_behaves_like 'settings for licensed features'
end
context "missing snowplow_collector_uri value when snowplow_enabled is true" do
it "returns a blank parameter error message" do
put api("/application/settings", admin), snowplow_enabled: true
......
......@@ -1187,7 +1187,7 @@ module API
class License < Grape::Entity
expose :key, :name, :nickname
expose :featured, as: :popular
expose :popular?, as: :popular
expose :url, as: :html_url
expose(:source_url) { |license| license.meta['source'] }
expose(:description) { |license| license.meta['description'] }
......
......@@ -151,6 +151,7 @@ module API
optional :email_additional_text, type: String, desc: 'Additional text added to the bottom of every email for legal/auditing/compliance reasons'
optional :help_text, type: String, desc: 'GitLab server administrator information'
optional :repository_size_limit, type: Integer, desc: 'Size limit per repository (MB)'
optional :file_template_project_id, type: Integer, desc: 'ID of project where instance-level file templates are stored.'
optional :repository_storages, type: Array[String], desc: 'A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random.'
optional :snowplow_enabled, type: Boolean, desc: 'Enable Snowplow'
given snowplow_enabled: ->(val) { val } do
......@@ -203,6 +204,10 @@ module API
unless ::License.feature_available?(:email_additional_text)
attrs = attrs.except(:email_additional_text)
end
unless ::License.feature_available?(:custom_file_templates)
attrs = attrs.except(:file_template_project_id)
end
## EE-only END: Remove unlicensed attributes
if ApplicationSettings::UpdateService.new(current_settings, current_user, attrs).execute
......
......@@ -16,31 +16,8 @@ module API
gitlab_version: 8.15
}
}.freeze
PROJECT_TEMPLATE_REGEX =
%r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
[\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
%r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]}xi.freeze
helpers do
def parsed_license_template
# We create a fresh Licensee::License object since we'll modify its
# content in place below.
template = Licensee::License.new(params[:name])
template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
fullname = params[:fullname].presence || current_user.try(:name)
template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
template
end
def render_response(template_type, template)
not_found!(template_type.to_s.singularize) unless template
present template, with: Entities::Template
......@@ -56,11 +33,12 @@ module API
use :pagination
end
get "templates/licenses" do
options = {
featured: declared(params)[:popular].present? ? true : nil
}
licences = ::Kaminari.paginate_array(Licensee::License.all(options))
present paginate(licences), with: Entities::License
popular = declared(params)[:popular]
popular = to_boolean(popular) if popular.present?
templates = LicenseTemplateFinder.new(popular: popular).execute
present paginate(::Kaminari.paginate_array(templates)), with: ::API::Entities::License
end
desc 'Get the text for a specific license' do
......@@ -71,9 +49,15 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do
not_found!('License') unless Licensee::License.find(declared(params)[:name])
templates = LicenseTemplateFinder.new.execute
template = templates.find { |template| template.key == params[:name] }
not_found!('License') unless template.present?
template = parsed_license_template
template.resolve!(
project_name: params[:project].presence,
fullname: params[:fullname].presence || current_user&.name
)
present template, with: ::API::Entities::License
end
......
......@@ -21,7 +21,7 @@ module Gitlab
def category_directory(category)
return @base_dir unless category.present?
@base_dir + @categories[category]
File.join(@base_dir, @categories[category])
end
class << self
......
......@@ -27,7 +27,7 @@ module Gitlab
directory = select_directory(file_name)
raise FileNotFoundError if directory.nil?
category_directory(directory) + file_name
File.join(category_directory(directory), file_name)
end
def list_files_for(dir)
......@@ -37,8 +37,8 @@ module Gitlab
entries = @repository.tree(:head, dir).entries
names = entries.map(&:name)
names.select { |f| f =~ self.class.filter_regex(@extension) }
paths = entries.map(&:path)
paths.select { |f| f =~ self.class.filter_regex(@extension) }
end
private
......@@ -47,10 +47,10 @@ module Gitlab
return [] unless @commit
# Insert root as directory
directories = ["", @categories.keys]
directories = ["", *@categories.keys]
directories.find do |category|
path = category_directory(category) + file_name
path = File.join(category_directory(category), file_name)
@repository.blob_at(@commit.id, path)
end
end
......
......@@ -6325,6 +6325,9 @@ msgstr ""
msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
msgstr ""
msgid "Set instance-wide template repository"
msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
......@@ -6818,6 +6821,9 @@ msgstr ""
msgid "Template"
msgstr ""
msgid "Templates"
msgstr ""
msgid "Terms of Service Agreement and Privacy Policy"
msgstr ""
......
require 'spec_helper'
describe LicenseTemplateFinder do
describe '#execute' do
subject(:result) { described_class.new(params).execute }
let(:categories) { categorised_licenses.keys }
let(:categorised_licenses) { result.group_by(&:category) }
context 'popular: true' do
let(:params) { { popular: true } }
it 'only returns popular licenses' do
expect(categories).to contain_exactly(:Popular)
expect(categorised_licenses[:Popular]).to be_present
end
end
context 'popular: false' do
let(:params) { { popular: false } }
it 'only returns unpopular licenses' do
expect(categories).to contain_exactly(:Other)
expect(categorised_licenses[:Other]).to be_present
end
end
context 'popular: nil' do
let(:params) { { popular: nil } }
it 'returns all licenses known by the Licensee gem' do
from_licensee = Licensee::License.all.map { |l| l.key }
expect(result.map(&:id)).to match_array(from_licensee)
end
it 'correctly copies all attributes' do
licensee = Licensee::License.all.first
found = result.find { |r| r.key == licensee.key }
aggregate_failures do
%i[key name content nickname url meta featured?].each do |k|
expect(found.public_send(k)).to eq(licensee.public_send(k))
end
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Template::Finders::RepoTemplateFinder do
set(:project) { create(:project, :repository) }
let(:categories) { { 'HTML' => 'html' } }
subject(:finder) { described_class.new(project, 'files/', '.html', categories) }
describe '#read' do
it 'returns the content of the given path' do
result = finder.read('files/html/500.html')
expect(result).to be_present
end
it 'raises an error if the path does not exist' do
expect { finder.read('does/not/exist') }.to raise_error(described_class::FileNotFoundError)
end
end
describe '#find' do
it 'returns the full path of the found template' do
result = finder.find('500')
expect(result).to eq('files/html/500.html')
end
end
describe '#list_files_for' do
it 'returns the full path of the found files' do
result = finder.list_files_for('files/html')
expect(result).to contain_exactly('files/html/500.html')
end
end
end
require 'spec_helper'
describe LicenseTemplate do
describe '#content' do
it 'calls a proc exactly once if provided' do
lazy = build_template(-> { 'bar' })
content = lazy.content
expect(content).to eq('bar')
expect(content.object_id).to eq(lazy.content.object_id)
content.replace('foo')
expect(lazy.content).to eq('foo')
end
it 'returns a string if provided' do
lazy = build_template('bar')
expect(lazy.content).to eq('bar')
end
end
describe '#resolve!' do
let(:content) do
<<~TEXT
Pretend License
[project]
Copyright (c) [year] [fullname]
TEXT
end
let(:expected) do
<<~TEXT
Pretend License
Foo Project
Copyright (c) 1985 Nick Thomas
TEXT
end
let(:template) { build_template(content) }
it 'updates placeholders in a copy of the template content' do
expect(template.content.object_id).to eq(content.object_id)
template.resolve!(project_name: "Foo Project", fullname: "Nick Thomas", year: "1985")
expect(template.content).to eq(expected)
expect(template.content.object_id).not_to eq(content.object_id)
end
end
def build_template(content)
described_class.new(id: 'foo', name: 'foo', category: :Other, content: content)
end
end
......@@ -56,6 +56,8 @@ describe API::Templates do
end
it 'returns a license template' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['key']).to eq('mit')
expect(json_response['name']).to eq('MIT License')
expect(json_response['nickname']).to be_nil
......@@ -181,6 +183,7 @@ describe API::Templates do
it 'replaces the copyright owner placeholder with the name of the current user' do
get api('/templates/licenses/mit', user)
expect(response).to have_gitlab_http_status(200)
expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}")
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment