提交 68b6846f 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 4db9eeb4
import { TestStatus } from '~/pipelines/constants';
import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility';
function iconForTestStatus(status) {
export function iconForTestStatus(status) {
switch (status) {
case 'success':
return 'status_success_borderless';
......
......@@ -173,7 +173,7 @@ module Issuable
private
def milestone_is_valid
errors.add(:milestone_id, message: "is invalid") if milestone_id.present? && !milestone_available?
errors.add(:milestone_id, message: "is invalid") if respond_to?(:milestone_id) && milestone_id.present? && !milestone_available?
end
def description_max_length_for_new_records_is_valid
......
......@@ -3,6 +3,8 @@
# Placeholder class for model that is implemented in EE
# It reserves '&' as a reference prefix, but the table does not exists in CE
class Epic < ApplicationRecord
self.ignored_columns += %i[milestone_id]
def self.link_reference_pattern
nil
end
......
......@@ -22,6 +22,8 @@ class PrometheusService < MonitoringService
after_save :clear_reactive_cache!
after_commit :track_events
def initialize_properties
if properties.nil?
self.properties = {}
......@@ -116,4 +118,22 @@ class PrometheusService < MonitoringService
true
end
def track_events
if enabled_manual_prometheus?
Gitlab::Tracking.event('cluster:services:prometheus', 'enabled_manual_prometheus')
elsif disabled_manual_prometheus?
Gitlab::Tracking.event('cluster:services:prometheus', 'disabled_manual_prometheus')
end
true
end
def enabled_manual_prometheus?
manual_configuration_changed? && manual_configuration?
end
def disabled_manual_prometheus?
manual_configuration_changed? && !manual_configuration?
end
end
......@@ -10,7 +10,13 @@ module Issuable
end
def execute
new_entity.update(milestone: cloneable_milestone, labels: cloneable_labels)
update_attributes = { labels: cloneable_labels }
milestone = cloneable_milestone
update_attributes[:milestone] = milestone if milestone.present?
new_entity.update(update_attributes)
copy_resource_label_events
end
......
---
title: Add snowplow events for APM
merge_request: 19463
author:
type: added
---
title: Update Container Registry naming restrictions to allow for sequential '-'
merge_request: 20318
author:
type: fixed
---
title: Remove milestone_id from epics
merge_request: 20187
author: Lee Tickett
type: other
......@@ -47,7 +47,6 @@ tables:
epics:
whitelist:
- id
- milestone_id
- group_id
- author_id
- assignee_id
......
......@@ -97,7 +97,7 @@ query {
### The root node
Any field defined in [`QueryType`](app/graphql/types/query_type.rb) will be exposed as a root node.
Any field defined in [`QueryType`](https://gitlab.com/gitlab-org/gitlab/tree/master/app/graphql/types/query_type.rb) will be exposed as a root node.
When retrieving child nodes use:
- the `edges { node { } }` syntax.
......
......@@ -18,6 +18,7 @@ SAST supports the following official analyzers:
- [`eslint`](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) (ESLint (JavaScript and React))
- [`flawfinder`](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder) (Flawfinder)
- [`gosec`](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) (Gosec)
- [`kubesec`](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec) (Kubesec)
- [`nodejs-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan) (NodeJsScan)
- [`phpcs-security-audit`](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit) (PHP CS security-audit)
- [`pmd-apex`](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) (PMD (Apex only))
......@@ -116,24 +117,24 @@ Custom analyzers are not spawned automatically when [Docker In Docker](index.md#
## Analyzers Data
| Property \ Tool | Apex | Bandit | Brakeman | ESLint security | Find Sec Bugs | Flawfinder | Go AST Scanner | NodeJsScan | Php CS Security Audit | Security code Scan (.NET) | TSLint Security | Sobelow |
| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :-------------: | :----------------: |
| Severity | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 |
| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Description | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | 𐄂 | ✓ | ✓ |
| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| End line | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
| Start column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | 𐄂 |
| End column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
| External id (e.g. CVE) | 𐄂 | 𐄂 | ⚠ | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal doc/explanation | ✓ | ⚠ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
| Solution | ✓ | 𐄂 | 𐄂 | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Confidence | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
| Affected item (e.g. class or package) | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Source code extract | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ |
| Property \ Tool | Apex | Bandit | Brakeman | ESLint security | Find Sec Bugs | Flawfinder | Go AST Scanner | Kubesec Scanner | NodeJsScan | Php CS Security Audit | Security code Scan (.NET) | Sobelow | TSLint Security |
| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :----------------: | :-------------: |
| Severity | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | ✓ |
| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Description | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ |
| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ | ✓ |
| End line | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
| Start column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | ✓ |
| End column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
| External id (e.g. CVE) | 𐄂 | 𐄂 | ⚠ | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal doc/explanation | ✓ | ⚠ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
| Solution | ✓ | 𐄂 | 𐄂 | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Affected item (e.g. class or package) | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Confidence | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
| Source code extract | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ |
- ✓ => we have that data
- ⚠ => we have that data but it's partially reliable, or we need to extract it from unstructured content
......
......@@ -73,6 +73,7 @@ The following table shows which languages, package managers and frameworks are s
| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT) |
| Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT) |
| JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8 |
| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6 |
| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1 |
| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8 |
| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3 |
......@@ -185,6 +186,22 @@ variables:
This will create individual `<analyzer-name>-sast` jobs for each analyzer that runs in your CI/CD pipeline.
#### Enabling kubesec analyzer
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12752) in GitLab Ultimate 12.6.
When [Docker in Docker is disabled](#disabling-docker-in-docker-for-sast),
you will need to set `SCAN_KUBERNETES_MANIFESTS` to `"true"` to enable the
kubesec analyzer. In `.gitlab-ci.yml`, define:
```yaml
include:
template: SAST.gitlab-ci.yml
variables:
SCAN_KUBERNETES_MANIFESTS: "true"
```
### Available variables
SAST can be [configured](#customizing-the-sast-settings) using environment variables.
......@@ -232,19 +249,20 @@ Timeout variables are not applicable for setups with [disabled Docker In Docker]
Some analyzers can be customized with environment variables.
| Environment variable | Analyzer | Description |
|-------------------------|----------|----------|
| `ANT_HOME` | spotbugs | The `ANT_HOME` environment variable. |
| `ANT_PATH` | spotbugs | Path to the `ant` executable. |
| `GRADLE_PATH` | spotbugs | Path to the `gradle` executable. |
| `JAVA_OPTS` | spotbugs | Additional arguments for the `java` executable. |
| `JAVA_PATH` | spotbugs | Path to the `java` executable. |
| `SAST_JAVA_VERSION` | spotbugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. |
| `MAVEN_CLI_OPTS` | spotbugs | Additional arguments for the `mvn` or `mvnw` executable. |
| `MAVEN_PATH` | spotbugs | Path to the `mvn` executable. |
| `MAVEN_REPO_PATH` | spotbugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). |
| `SBT_PATH` | spotbugs | Path to the `sbt` executable. |
| `FAIL_NEVER` | spotbugs | Set to `1` to ignore compilation failure. |
| Environment variable | Analyzer | Description |
|-----------------------------|----------|-------------|
| `SCAN_KUBERNETES_MANIFESTS` | kubesec | Set to `"true"` to scan Kubernetes manifests when [Docker in Docker](#disabling-docker-in-docker-for-sast) is disabled. |
| `ANT_HOME` | spotbugs | The `ANT_HOME` environment variable. |
| `ANT_PATH` | spotbugs | Path to the `ant` executable. |
| `GRADLE_PATH` | spotbugs | Path to the `gradle` executable. |
| `JAVA_OPTS` | spotbugs | Additional arguments for the `java` executable. |
| `JAVA_PATH` | spotbugs | Path to the `java` executable. |
| `SAST_JAVA_VERSION` | spotbugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. |
| `MAVEN_CLI_OPTS` | spotbugs | Additional arguments for the `mvn` or `mvnw` executable. |
| `MAVEN_PATH` | spotbugs | Path to the `mvn` executable. |
| `MAVEN_REPO_PATH` | spotbugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). |
| `SBT_PATH` | spotbugs | Path to the `sbt` executable. |
| `FAIL_NEVER` | spotbugs | Set to `1` to ignore compilation failure. |
#### Custom environment variables
......
......@@ -29,10 +29,11 @@ module Gitlab
def execute!
result = execute_steps
if result[:status] == :success
::Gitlab::Tracking.event("self_monitoring", "project_created")
result
elsif STEPS_ALLOWED_TO_FAIL.include?(result[:last_step])
::Gitlab::Tracking.event("self_monitoring", "project_created")
success
else
raise StandardError, result[:message]
......
......@@ -19,7 +19,7 @@ module Gitlab
# See https://github.com/docker/distribution/blob/master/reference/regexp.go.
#
def container_repository_name_regex
@container_repository_regex ||= %r{\A[a-z0-9]+((?:[._/]|__|[-])[a-z0-9]+)*\Z}
@container_repository_regex ||= %r{\A[a-z0-9]+((?:[._/]|__|[-]{0,10})[a-z0-9]+)*\Z}
end
##
......
......@@ -13,7 +13,7 @@ describe Projects::TagsController do
end
it 'returns the tags for the page' do
expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
expect(assigns(:tags).map(&:name)).to include('v1.1.0', 'v1.0.0')
end
it 'returns releases matching those tags' do
......
......@@ -837,8 +837,7 @@ describe ProjectsController do
get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' }
expect(json_response['Branches']).to include('master')
expect(json_response['Tags'].first).to eq('v1.1.0')
expect(json_response['Tags'].last).to eq('v1.0.0')
expect(json_response['Tags']).to include('v1.0.0')
expect(json_response['Commits']).to be_nil
end
......
......@@ -21,23 +21,21 @@ describe 'Project > Tags', :js do
context 'page with tags list' do
it 'shows tag name' do
page.within first('.tags > .content-list > li') do
expect(page.find('.row-main-content')).to have_content 'v1.1.0 Version 1.1.0'
end
expect(page).to have_content 'v1.1.0 Version 1.1.0'
end
it 'shows tag edit button' do
page.within first('.tags > .content-list > li') do
edit_btn = page.find('.row-fixed-content.controls a.btn-edit')
page.within '.tags > .content-list' do
edit_btn = page.find("li > .row-fixed-content.controls a.btn-edit[href='/#{project.full_path}/-/tags/v1.1.0/release/edit']")
expect(edit_btn['href']).to have_content '/tags/v1.1.0/release/edit'
expect(edit_btn['href']).to end_with("/#{project.full_path}/-/tags/v1.1.0/release/edit")
end
end
end
context 'edit tag release notes' do
before do
find('.tags > .content-list > li:first-child .row-fixed-content.controls a.btn-edit').click
page.find("li > .row-fixed-content.controls a.btn-edit[href='/#{project.full_path}/-/tags/v1.1.0/release/edit']").click
end
it 'shows tag name header' do
......
......@@ -17,7 +17,7 @@ describe 'Developer deletes tag' do
it 'deletes the tag' do
expect(page).to have_content 'v1.1.0'
delete_first_tag
delete_tag 'v1.1.0'
expect(page).not_to have_content 'v1.1.0'
end
......@@ -46,15 +46,15 @@ describe 'Developer deletes tag' do
end
it 'shows the error message' do
delete_first_tag
delete_tag 'v1.1.0'
expect(page).to have_content('Do not delete tags')
end
end
def delete_first_tag
def delete_tag(tag)
page.within('.content') do
accept_confirm { first('.btn-remove').click }
accept_confirm { find("li > .row-fixed-content.controls a.btn-remove[href='/#{project.full_path}/-/tags/#{tag}']").click }
end
end
end
......@@ -15,9 +15,7 @@ describe 'Developer updates tag' do
context 'from the tags list page' do
it 'updates the release notes' do
page.within(first('.content-list .controls')) do
click_link 'Edit release notes'
end
find("li > .row-fixed-content.controls a.btn-edit[href='/#{project.full_path}/-/tags/v1.1.0/release/edit']").click
fill_in 'release_description', with: 'Awesome release notes'
click_button 'Save changes'
......
......@@ -95,24 +95,25 @@ describe TagsFinder do
end
context 'filter and sort' do
it 'filters tags by name and sorts by recently_updated' do
params = { sort: 'updated_desc', search: 'v1' }
tags_finder = described_class.new(repository, params)
let(:tags_to_compare) { %w[v1.0.0 v1.1.0] }
subject { described_class.new(repository, params).execute.select { |tag| tags_to_compare.include?(tag.name) } }
result = tags_finder.execute
context 'when sort by updated_desc' do
let(:params) { { sort: 'updated_desc', search: 'v1' } }
expect(result.first.name).to eq('v1.1.0')
expect(result.count).to eq(2)
it 'filters tags by name' do
expect(subject.first.name).to eq('v1.1.0')
expect(subject.count).to eq(2)
end
end
it 'filters tags by name and sorts by last_updated' do
params = { sort: 'updated_asc', search: 'v1' }
tags_finder = described_class.new(repository, params)
result = tags_finder.execute
context 'when sort by updated_asc' do
let(:params) { { sort: 'updated_asc', search: 'v1' } }
expect(result.first.name).to eq('v1.0.0')
expect(result.count).to eq(2)
it 'filters tags by name' do
expect(subject.first.name).to eq('v1.0.0')
expect(subject.count).to eq(2)
end
end
end
end
......
# frozen_string_literal: true
require "spec_helper"
describe Projects::PipelinesController, "(JavaScript fixtures)", type: :controller do
include JavaScriptFixturesHelpers
let(:namespace) { create(:namespace, name: "frontend-fixtures") }
let(:project) { create(:project, :repository, namespace: namespace, path: "pipelines-project") }
let(:commit) { create(:commit, project: project) }
let(:user) { create(:user, developer_projects: [project], email: commit.author_email) }
let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project, user: user) }
render_views
before do
sign_in(user)
stub_feature_flags(junit_pipeline_view: true)
end
it "pipelines/test_report.json" do
get :test_report, params: {
namespace_id: project.namespace,
project_id: project,
id: pipeline.id
}, format: :json
expect(response).to be_successful
end
end
import { formatTime } from '~/lib/utils/datetime_utility';
import { TestStatus } from '~/pipelines/constants';
export const testCases = [
{
classname: 'spec.test_spec',
execution_time: 0.000748,
name: 'Test#subtract when a is 1 and b is 2 raises an error',
stack_trace: null,
status: TestStatus.SUCCESS,
system_output: null,
},
{
classname: 'spec.test_spec',
execution_time: 0.000064,
name: 'Test#subtract when a is 2 and b is 1 returns correct result',
stack_trace: null,
status: TestStatus.SUCCESS,
system_output: null,
},
{
classname: 'spec.test_spec',
execution_time: 0.009292,
name: 'Test#sum when a is 1 and b is 2 returns summary',
stack_trace: null,
status: TestStatus.FAILED,
system_output:
"Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'",
},
{
classname: 'spec.test_spec',
execution_time: 0.00018,
name: 'Test#sum when a is 100 and b is 200 returns summary',
stack_trace: null,
status: TestStatus.FAILED,
system_output:
"Failure/Error: is_expected.to eq(300)\n\n expected: 300\n got: -100\n\n (compared using ==)\n./spec/test_spec.rb:21:in `block (4 levels) in <top (required)>'",
},
export default [
{
classname: 'spec.test_spec',
execution_time: 0,
......@@ -45,79 +10,3 @@ export const testCases = [
system_output: null,
},
];
export const testCasesFormatted = [
{
...testCases[2],
icon: 'status_failed_borderless',
formattedTime: formatTime(testCases[0].execution_time * 1000),
},
{
...testCases[3],
icon: 'status_failed_borderless',
formattedTime: formatTime(testCases[1].execution_time * 1000),
},
{
...testCases[4],
icon: 'status_skipped_borderless',
formattedTime: formatTime(testCases[2].execution_time * 1000),
},
{
...testCases[0],
icon: 'status_success_borderless',
formattedTime: formatTime(testCases[3].execution_time * 1000),
},
{
...testCases[1],
icon: 'status_success_borderless',
formattedTime: formatTime(testCases[4].execution_time * 1000),
},
];
export const testSuites = [
{
error_count: 0,
failed_count: 2,
name: 'rspec:osx',
skipped_count: 0,
success_count: 2,
test_cases: testCases,
total_count: 4,
total_time: 60,
},
{
error_count: 0,
failed_count: 10,
name: 'rspec:osx',
skipped_count: 0,
success_count: 50,
test_cases: [],
total_count: 60,
total_time: 0.010284,
},
];
export const testSuitesFormatted = testSuites.map(x => ({
...x,
formattedTime: formatTime(x.total_time * 1000),
}));
export const testReports = {
error_count: 0,
failed_count: 2,
skipped_count: 0,
success_count: 2,
test_suites: testSuites,
total_count: 4,
total_time: 0.010284,
};
export const testReportsWithNoSuites = {
error_count: 0,
failed_count: 2,
skipped_count: 0,
success_count: 2,
test_suites: [],
total_count: 4,
total_time: 0.010284,
};
......@@ -2,10 +2,10 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import * as actions from '~/pipelines/stores/test_reports/actions';
import * as types from '~/pipelines/stores/test_reports/mutation_types';
import { getJSONFixture } from 'helpers/fixtures';
import { TEST_HOST } from '../../../helpers/test_constants';
import testAction from '../../../helpers/vuex_action_helper';
import createFlash from '~/flash';
import { testReports } from '../mock_data';
jest.mock('~/flash.js');
......@@ -13,6 +13,8 @@ describe('Actions TestReports Store', () => {
let mock;
let state;
const testReports = getJSONFixture('pipelines/test_report.json');
const endpoint = `${TEST_HOST}/test_reports.json`;
const defaultState = {
endpoint,
......
import * as getters from '~/pipelines/stores/test_reports/getters';
import { testReports, testSuitesFormatted, testCasesFormatted } from '../mock_data';
import { iconForTestStatus } from '~/pipelines/stores/test_reports/utils';
import { getJSONFixture } from 'helpers/fixtures';
describe('Getters TestReports Store', () => {
let state;
const testReports = getJSONFixture('pipelines/test_report.json');
const defaultState = {
testReports,
selectedSuite: testReports.test_suites[0],
......@@ -28,7 +31,13 @@ describe('Getters TestReports Store', () => {
it('should return the test suites', () => {
setupState();
expect(getters.getTestSuites(state)).toEqual(testSuitesFormatted);
const suites = getters.getTestSuites(state);
const expected = testReports.test_suites.map(x => ({
...x,
formattedTime: '00:00:00',
}));
expect(suites).toEqual(expected);
});
it('should return an empty array when testReports is empty', () => {
......@@ -42,7 +51,14 @@ describe('Getters TestReports Store', () => {
it('should return the test cases inside the suite', () => {
setupState();
expect(getters.getSuiteTests(state)).toEqual(testCasesFormatted);
const cases = getters.getSuiteTests(state);
const expected = testReports.test_suites[0].test_cases.map(x => ({
...x,
formattedTime: '00:00:00',
icon: iconForTestStatus(x.status),
}));
expect(cases).toEqual(expected);
});
it('should return an empty array when testReports is empty', () => {
......
import * as types from '~/pipelines/stores/test_reports/mutation_types';
import mutations from '~/pipelines/stores/test_reports/mutations';
import { testReports, testSuites } from '../mock_data';
import { getJSONFixture } from 'helpers/fixtures';
describe('Mutations TestReports Store', () => {
let mockState;
const testReports = getJSONFixture('pipelines/test_report.json');
const defaultState = {
endpoint: '',
testReports: {},
......@@ -27,7 +29,7 @@ describe('Mutations TestReports Store', () => {
describe('set reports', () => {
it('should set testReports', () => {
const expectedState = Object.assign({}, mockState, { testReports });
const expectedState = { ...mockState, testReports };
mutations[types.SET_REPORTS](mockState, testReports);
expect(mockState.testReports).toEqual(expectedState.testReports);
......@@ -36,10 +38,10 @@ describe('Mutations TestReports Store', () => {
describe('set selected suite', () => {
it('should set selectedSuite', () => {
const expectedState = Object.assign({}, mockState, { selectedSuite: testSuites[0] });
mutations[types.SET_SELECTED_SUITE](mockState, testSuites[0]);
const selectedSuite = testReports.test_suites[0];
mutations[types.SET_SELECTED_SUITE](mockState, selectedSuite);
expect(mockState.selectedSuite).toEqual(expectedState.selectedSuite);
expect(mockState.selectedSuite).toEqual(selectedSuite);
});
});
......
import Vuex from 'vuex';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
import { shallowMount } from '@vue/test-utils';
import { testReports } from './mock_data';
import * as actions from '~/pipelines/stores/test_reports/actions';
import { getJSONFixture } from 'helpers/fixtures';
describe('Test reports app', () => {
let wrapper;
let store;
const testReports = getJSONFixture('pipelines/test_report.json');
const loadingSpinner = () => wrapper.find('.js-loading-spinner');
const testsDetail = () => wrapper.find('.js-tests-detail');
const noTestsToShow = () => wrapper.find('.js-no-tests-to-show');
......
......@@ -3,18 +3,26 @@ import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue
import * as getters from '~/pipelines/stores/test_reports/getters';
import { TestStatus } from '~/pipelines/constants';
import { shallowMount } from '@vue/test-utils';
import { testSuites, testCases } from './mock_data';
import { getJSONFixture } from 'helpers/fixtures';
import skippedTestCases from './mock_data';
describe('Test reports suite table', () => {
let wrapper;
let store;
const {
test_suites: [testSuite],
} = getJSONFixture('pipelines/test_report.json');
testSuite.test_cases = [...testSuite.test_cases, ...skippedTestCases];
const testCases = testSuite.test_cases;
const noCasesMessage = () => wrapper.find('.js-no-test-cases');
const allCaseRows = () => wrapper.findAll('.js-case-row');
const findCaseRowAtIndex = index => wrapper.findAll('.js-case-row').at(index);
const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`);
const createComponent = (suite = testSuites[0]) => {
const createComponent = (suite = testSuite) => {
store = new Vuex.Store({
state: {
selectedSuite: suite,
......
import Summary from '~/pipelines/components/test_reports/test_summary.vue';
import { mount } from '@vue/test-utils';
import { testSuites } from './mock_data';
import { getJSONFixture } from 'helpers/fixtures';
describe('Test reports summary', () => {
let wrapper;
const {
test_suites: [testSuite],
} = getJSONFixture('pipelines/test_report.json');
const backButton = () => wrapper.find('.js-back-button');
const totalTests = () => wrapper.find('.js-total-tests');
const failedTests = () => wrapper.find('.js-failed-tests');
......@@ -13,7 +17,7 @@ describe('Test reports summary', () => {
const duration = () => wrapper.find('.js-duration');
const defaultProps = {
report: testSuites[0],
report: testSuite,
showBack: false,
};
......@@ -72,7 +76,7 @@ describe('Test reports summary', () => {
});
it('displays the correctly formatted duration', () => {
expect(duration().text()).toBe('00:01:00');
expect(duration().text()).toBe('00:00:00');
});
});
});
......@@ -2,7 +2,7 @@ import Vuex from 'vuex';
import SummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters';
import { mount, createLocalVue } from '@vue/test-utils';
import { testReports, testReportsWithNoSuites } from './mock_data';
import { getJSONFixture } from 'helpers/fixtures';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -11,6 +11,8 @@ describe('Test reports summary table', () => {
let wrapper;
let store;
const testReports = getJSONFixture('pipelines/test_report.json');
const allSuitesRows = () => wrapper.findAll('.js-suite-row');
const noSuitesToShow = () => wrapper.find('.js-no-tests-suites');
......@@ -44,7 +46,7 @@ describe('Test reports summary table', () => {
describe('when there are no test suites', () => {
beforeEach(() => {
createComponent({ testReportsWithNoSuites });
createComponent({ test_suites: [] });
});
it('displays the no suites to show message', () => {
......
import Vue from 'vue';
import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
describe('Time ago with tooltip component', () => {
let TimeagoTooltip;
let vm;
beforeEach(() => {
TimeagoTooltip = Vue.extend(timeagoTooltip);
});
const buildVm = (propsData = {}) => {
vm = shallowMount(TimeAgoTooltip, {
attachToDocument: true,
sync: false,
propsData,
localVue: createLocalVue(),
});
};
const timestamp = '2017-05-08T14:57:39.781Z';
afterEach(() => {
vm.$destroy();
vm.destroy();
});
it('should render timeago with a bootstrap tooltip', () => {
vm = new TimeagoTooltip({
propsData: {
time: '2017-05-08T14:57:39.781Z',
},
}).$mount();
expect(vm.$el.tagName).toEqual('TIME');
expect(vm.$el.getAttribute('data-original-title')).toEqual(
formatDate('2017-05-08T14:57:39.781Z'),
);
buildVm({
time: timestamp,
});
const timeago = getTimeago();
expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z'));
expect(vm.attributes('data-original-title')).toEqual(formatDate(timestamp));
expect(vm.text()).toEqual(timeago.format(timestamp));
});
it('should render provided html class', () => {
vm = new TimeagoTooltip({
propsData: {
time: '2017-05-08T14:57:39.781Z',
cssClass: 'foo',
},
}).$mount();
buildVm({
time: timestamp,
cssClass: 'foo',
});
expect(vm.$el.classList.contains('foo')).toEqual(true);
expect(vm.classes()).toContain('foo');
});
});
# frozen_string_literal: true
require 'spec_helper'
describe 'Auto-DevOps.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') }
describe 'the created pipeline' do
let(:user) { create(:admin) }
let(:default_branch) { 'master' }
let(:pipeline_branch) { default_branch }
let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
let(:pipeline) { service.execute!(:push) }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
stub_ci_pipeline_yaml_file(template.content)
allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
allow(project).to receive(:default_branch).and_return(default_branch)
end
it 'creates a build and a test job' do
expect(build_names).to include('build', 'test')
end
context 'when the project has no active cluster' do
it 'only creates a build and a test stage' do
expect(pipeline.stages_names).to eq(%w(build test))
end
it 'does not create any deployment-related builds' do
expect(build_names).not_to include('production')
expect(build_names).not_to include('production_manual')
expect(build_names).not_to include('staging')
expect(build_names).not_to include('canary')
expect(build_names).not_to include('review')
expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
end
end
context 'when the project has an active cluster' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) }
before do
allow(cluster).to receive(:active?).and_return(true)
end
describe 'deployment-related builds' do
context 'on default branch' do
it 'does not include rollout jobs besides production' do
expect(build_names).to include('production')
expect(build_names).not_to include('production_manual')
expect(build_names).not_to include('staging')
expect(build_names).not_to include('canary')
expect(build_names).not_to include('review')
expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
end
context 'when STAGING_ENABLED=1' do
before do
create(:ci_variable, project: project, key: 'STAGING_ENABLED', value: '1')
end
it 'includes a staging job and a production_manual job' do
expect(build_names).not_to include('production')
expect(build_names).to include('production_manual')
expect(build_names).to include('staging')
expect(build_names).not_to include('canary')
expect(build_names).not_to include('review')
expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
end
end
context 'when CANARY_ENABLED=1' do
before do
create(:ci_variable, project: project, key: 'CANARY_ENABLED', value: '1')
end
it 'includes a canary job and a production_manual job' do
expect(build_names).not_to include('production')
expect(build_names).to include('production_manual')
expect(build_names).not_to include('staging')
expect(build_names).to include('canary')
expect(build_names).not_to include('review')
expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
end
end
end
context 'outside of default branch' do
let(:pipeline_branch) { 'patch-1' }
before do
project.repository.create_branch(pipeline_branch)
end
it 'does not include rollout jobs besides review' do
expect(build_names).not_to include('production')
expect(build_names).not_to include('production_manual')
expect(build_names).not_to include('staging')
expect(build_names).not_to include('canary')
expect(build_names).to include('review')
expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
end
end
end
end
end
end
......@@ -279,5 +279,11 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
end
end
end
it "tracks successful install" do
expect(Gitlab::Tracking).to receive(:event).with("self_monitoring", "project_created")
result
end
end
end
......@@ -61,6 +61,12 @@ describe Gitlab::Regex do
it { is_expected.to match('my/image') }
it { is_expected.to match('my/awesome/image-1') }
it { is_expected.to match('my/awesome/image.test') }
it { is_expected.to match('my/awesome/image--test') }
# docker distribution allows for infinite `-`
# https://github.com/docker/distribution/blob/master/reference/regexp.go#L13
# but we have a range of 0,10 to add a reasonable limit.
it { is_expected.not_to match('my/image-----------test') }
it { is_expected.not_to match('my/image-.test') }
it { is_expected.not_to match('.my/image') }
it { is_expected.not_to match('my/image.') }
end
......
......@@ -262,4 +262,28 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
end
end
describe '#track_events after_commit callback' do
before do
allow(service).to receive(:prometheus_available?).and_return(true)
end
context "enabling manual_configuration" do
it "tracks enable event" do
service.update!(manual_configuration: false)
expect(Gitlab::Tracking).to receive(:event).with('cluster:services:prometheus', 'enabled_manual_prometheus')
service.update!(manual_configuration: true)
end
it "tracks disable event" do
service.update!(manual_configuration: true)
expect(Gitlab::Tracking).to receive(:event).with('cluster:services:prometheus', 'disabled_manual_prometheus')
service.update!(manual_configuration: false)
end
end
end
end
......@@ -66,14 +66,16 @@ describe Repository do
end
describe 'tags_sorted_by' do
let(:tags_to_compare) { %w[v1.0.0 v1.1.0] }
context 'name_desc' do
subject { repository.tags_sorted_by('name_desc').map(&:name) }
subject { repository.tags_sorted_by('name_desc').map(&:name) & tags_to_compare }
it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
end
context 'name_asc' do
subject { repository.tags_sorted_by('name_asc').map(&:name) }
subject { repository.tags_sorted_by('name_asc').map(&:name) & tags_to_compare }
it { is_expected.to eq(['v1.0.0', 'v1.1.0']) }
end
......@@ -115,7 +117,7 @@ describe Repository do
context 'annotated tag pointing to a blob' do
let(:annotated_tag_name) { 'annotated-tag' }
subject { repository.tags_sorted_by('updated_asc').map(&:name) }
subject { repository.tags_sorted_by('updated_asc').map(&:name) & (tags_to_compare + [annotated_tag_name]) }
before do
options = { message: 'test tag message\n',
......
......@@ -7,6 +7,7 @@ describe API::Tags do
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
let(:tag_name) { project.repository.find_tag('v1.1.0').name }
let(:tag_message) { project.repository.find_tag('v1.1.0').message }
let(:project_id) { project.id }
let(:current_user) { nil }
......@@ -75,7 +76,7 @@ describe API::Tags do
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/tags')
expect(response).to include_pagination_headers
expect(json_response.first['name']).to eq(tag_name)
expect(json_response.map { |r| r['name'] }).to include(tag_name)
end
context 'when repository is disabled' do
......@@ -135,9 +136,10 @@ describe API::Tags do
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/tags')
expect(response).to include_pagination_headers
expect(json_response.first['name']).to eq(tag_name)
expect(json_response.first['message']).to eq('Version 1.1.0')
expect(json_response.first['release']['description']).to eq(description)
expected_tag = json_response.find { |r| r['name'] == tag_name }
expect(expected_tag['message']).to eq(tag_message)
expect(expected_tag['release']['description']).to eq(description)
end
end
end
......
......@@ -970,10 +970,10 @@
"@sentry/types" "5.7.1"
tslib "^1.9.3"
"@sourcegraph/code-host-integration@^0.0.13":
version "0.0.13"
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.13.tgz#4fd5fe1e0088c63b2a26be231c5a2a4ca79b1596"
integrity sha512-IjF9gb9e8dG8p12DKg5Z7UMOVQO/ClH3AyMCPfX/qH7DH/0b55WH6stYVqZu6y776quFonO4Z9gWYM8pQZjzKw==
"@sourcegraph/code-host-integration@^0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.14.tgz#e12b08371dc37bf4a468450b008c6e167705e1a8"
integrity sha512-S4+K+3RKFd49Btl1D9LOdWXROgXevUwOBwp+vDUuGgzT2d6Y+qjalUJ0t8CjbYzdBdJun+2/Zi1+SXfm+S+xVg==
"@types/anymatch@*":
version "1.3.0"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册