Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb125
-rw-r--r--spec/factories/evidences.rb7
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb3
-rw-r--r--spec/finders/todos_finder_spec.rb41
-rw-r--r--spec/fixtures/api/schemas/evidences/author.json14
-rw-r--r--spec/fixtures/api/schemas/evidences/evidence.json11
-rw-r--r--spec/fixtures/api/schemas/evidences/issue.json5
-rw-r--r--spec/fixtures/api/schemas/evidences/milestone.json4
-rw-r--r--spec/fixtures/api/schemas/evidences/project.json2
-rw-r--r--spec/fixtures/api/schemas/evidences/release.json6
-rw-r--r--spec/fixtures/api/schemas/job/build_trace.json31
-rw-r--r--spec/fixtures/api/schemas/job/build_trace_line.json18
-rw-r--r--spec/fixtures/api/schemas/job/build_trace_line_content.json11
-rw-r--r--spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb21
-rw-r--r--spec/graphql/resolvers/todo_resolver_spec.rb113
-rw-r--r--spec/graphql/types/query_type_spec.rb2
-rw-r--r--spec/graphql/types/todo_type_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb54
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb25
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml7
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml6
-rw-r--r--spec/models/ci/build_trace_spec.rb53
-rw-r--r--spec/models/evidence_spec.rb87
-rw-r--r--spec/models/group_spec.rb17
-rw-r--r--spec/models/hooks/web_hook_spec.rb11
-rw-r--r--spec/models/release_spec.rb20
-rw-r--r--spec/models/todo_spec.rb4
-rw-r--r--spec/policies/todo_policy_spec.rb47
-rw-r--r--spec/serializers/build_trace_entity_spec.rb63
-rw-r--r--spec/serializers/evidences/author_entity_spec.rb13
-rw-r--r--spec/serializers/evidences/evidence_entity_spec.rb14
-rw-r--r--spec/serializers/evidences/evidence_serializer_spec.rb9
-rw-r--r--spec/serializers/evidences/issue_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/milestone_entity_spec.rb2
-rw-r--r--spec/support/shared_examples/evidence_updated_exposed_fields.rb29
-rw-r--r--spec/workers/create_evidence_worker_spec.rb11
36 files changed, 767 insertions, 134 deletions
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 53d32665b0c..90ccb884927 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -527,6 +527,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
describe 'GET trace.json' do
before do
+ stub_feature_flags(job_log_json: true)
get_trace
end
@@ -535,8 +536,119 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
it 'returns a trace' do
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/build_trace')
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
+ expect(json_response['state']).to be_present
+ expect(json_response['append']).not_to be_nil
+ expect(json_response['truncated']).not_to be_nil
+ expect(json_response['size']).to be_present
+ expect(json_response['total']).to be_present
+ expect(json_response['lines'].count).to be_positive
+ end
+ end
+
+ context 'when job has a trace' do
+ let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
+
+ it 'returns a trace' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/build_trace')
+ expect(json_response['id']).to eq job.id
+ expect(json_response['status']).to eq job.status
+ expect(json_response['lines']).to eq [{ 'content' => [{ 'text' => 'BUILD TRACE' }], 'offset' => 0 }]
+ end
+ end
+
+ context 'when job has no traces' do
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'returns no traces' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/build_trace')
+ expect(json_response['id']).to eq job.id
+ expect(json_response['status']).to eq job.status
+ expect(json_response['lines']).to be_nil
+ end
+ end
+
+ context 'when job has a trace with ANSI sequence and Unicode' do
+ let(:job) { create(:ci_build, :unicode_trace_live, pipeline: pipeline) }
+
+ it 'returns a trace with Unicode' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/build_trace')
+ expect(json_response['id']).to eq job.id
+ expect(json_response['status']).to eq job.status
+ expect(json_response['lines'].flat_map {|l| l['content'].map { |c| c['text'] } }).to include("ヾ(´༎ຶД༎ຶ`)ノ")
+ end
+ end
+
+ context 'when trace artifact is in ObjectStorage' do
+ let(:url) { 'http://object-storage/trace' }
+ let(:file_path) { expand_fixture_path('trace/sample_trace') }
+ let!(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
+
+ before do
+ allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false }
+ allow_any_instance_of(JobArtifactUploader).to receive(:url) { url }
+ allow_any_instance_of(JobArtifactUploader).to receive(:size) { File.size(file_path) }
+ end
+
+ context 'when there are no network issues' do
+ before do
+ stub_remote_url_206(url, file_path)
+
+ get_trace
+ end
+
+ it 'returns a trace' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['id']).to eq job.id
+ expect(json_response['status']).to eq job.status
+ expect(json_response['lines'].count).to be_positive
+ end
+ end
+
+ context 'when there is a network issue' do
+ before do
+ stub_remote_url_500(url)
+ end
+
+ it 'returns a trace' do
+ expect { get_trace }.to raise_error(Gitlab::HttpIO::FailedToGetChunkError)
+ end
+ end
+ end
+
+ def get_trace
+ get :trace,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ },
+ format: :json
+ end
+ end
+
+ describe 'GET legacy trace.json' do
+ before do
+ get_trace
+ end
+
+ context 'when job has a trace artifact' do
+ let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
+
+ it 'returns a trace' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['id']).to eq job.id
+ expect(json_response['status']).to eq job.status
+ expect(json_response['state']).to be_present
+ expect(json_response['append']).not_to be_nil
+ expect(json_response['truncated']).not_to be_nil
+ expect(json_response['size']).to be_present
+ expect(json_response['total']).to be_present
expect(json_response['html']).to eq(job.trace.html)
end
end
@@ -612,12 +724,13 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
def get_trace
- get :trace, params: {
- namespace_id: project.namespace,
- project_id: project,
- id: job.id
- },
- format: :json
+ get :trace,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ },
+ format: :json
end
end
diff --git a/spec/factories/evidences.rb b/spec/factories/evidences.rb
new file mode 100644
index 00000000000..964f232a1c9
--- /dev/null
+++ b/spec/factories/evidences.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :evidence do
+ release
+ end
+end
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index 44309a9c4bf..ae506b66a86 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -10,6 +10,7 @@ describe 'Project Jobs Permissions' do
let!(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
before do
+ stub_feature_flags(job_log_json: true)
sign_in(user)
project.enable_ci
@@ -69,7 +70,7 @@ describe 'Project Jobs Permissions' do
it_behaves_like 'recent job page details responds with status', 200 do
it 'renders job details', :js do
expect(page).to have_content "Job ##{job.id}"
- expect(page).to have_css '.js-build-trace'
+ expect(page).to have_css '.log-line'
end
end
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 5d284f4cf17..044e135fa0b 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -16,6 +16,10 @@ describe TodosFinder do
end
describe '#execute' do
+ it 'returns no todos if user is nil' do
+ expect(described_class.new(nil, {}).execute).to be_empty
+ end
+
context 'filtering' do
let!(:todo1) { create(:todo, user: user, project: project, target: issue) }
let!(:todo2) { create(:todo, user: user, group: group, target: merge_request) }
@@ -97,14 +101,39 @@ describe TodosFinder do
end
end
- context 'with subgroups' do
- let(:subgroup) { create(:group, parent: group) }
- let!(:todo3) { create(:todo, user: user, group: subgroup, target: issue) }
+ context 'by groups' do
+ context 'with subgroups' do
+ let(:subgroup) { create(:group, parent: group) }
+ let!(:todo3) { create(:todo, user: user, group: subgroup, target: issue) }
+
+ it 'returns todos from subgroups when filtered by a group' do
+ todos = finder.new(user, { group_id: group.id }).execute
+
+ expect(todos).to match_array([todo1, todo2, todo3])
+ end
+ end
+
+ context 'filtering for multiple groups' do
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:group3) { create(:group) }
+
+ let!(:todo1) { create(:todo, user: user, project: project, target: issue) }
+ let!(:todo2) { create(:todo, user: user, group: group, target: merge_request) }
+ let!(:todo3) { create(:todo, user: user, group: group2, target: merge_request) }
+
+ let(:subgroup1) { create(:group, parent: group) }
+ let!(:todo4) { create(:todo, user: user, group: subgroup1, target: issue) }
- it 'returns todos from subgroups when filtered by a group' do
- todos = finder.new(user, { group_id: group.id }).execute
+ let(:subgroup2) { create(:group, parent: group2) }
+ let!(:todo5) { create(:todo, user: user, group: subgroup2, target: issue) }
- expect(todos).to match_array([todo1, todo2, todo3])
+ let!(:todo6) { create(:todo, user: user, group: group3, target: issue) }
+
+ it 'returns the expected groups' do
+ todos = finder.new(user, { group_id: [group.id, group2.id] }).execute
+
+ expect(todos).to match_array([todo1, todo2, todo3, todo4, todo5])
+ end
end
end
end
diff --git a/spec/fixtures/api/schemas/evidences/author.json b/spec/fixtures/api/schemas/evidences/author.json
deleted file mode 100644
index 1b49446900a..00000000000
--- a/spec/fixtures/api/schemas/evidences/author.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "type": "object",
- "required": [
- "id",
- "name",
- "email"
- ],
- "properties": {
- "id": { "type": "integer" },
- "name": { "type": "string" },
- "email": { "type": "string" }
- },
- "additionalProperties": false
-}
diff --git a/spec/fixtures/api/schemas/evidences/evidence.json b/spec/fixtures/api/schemas/evidences/evidence.json
new file mode 100644
index 00000000000..ea3861258e1
--- /dev/null
+++ b/spec/fixtures/api/schemas/evidences/evidence.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "required": [
+ "release"
+ ],
+ "properties": {
+ "release": { "$ref": "release.json" }
+ },
+ "additionalProperties": false
+}
+
diff --git a/spec/fixtures/api/schemas/evidences/issue.json b/spec/fixtures/api/schemas/evidences/issue.json
index 10e90dff455..fd9daf17ab8 100644
--- a/spec/fixtures/api/schemas/evidences/issue.json
+++ b/spec/fixtures/api/schemas/evidences/issue.json
@@ -14,13 +14,12 @@
"properties": {
"id": { "type": "integer" },
"title": { "type": "string" },
- "description": { "type": "string" },
- "author": { "$ref": "author.json" },
+ "description": { "type": ["string", "null"] },
"state": { "type": "string" },
"iid": { "type": "integer" },
"confidential": { "type": "boolean" },
"created_at": { "type": "date" },
- "due_date": { "type": "date" }
+ "due_date": { "type": ["date", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/evidences/milestone.json b/spec/fixtures/api/schemas/evidences/milestone.json
index 91f0f48bd4c..ab27fdecde2 100644
--- a/spec/fixtures/api/schemas/evidences/milestone.json
+++ b/spec/fixtures/api/schemas/evidences/milestone.json
@@ -13,11 +13,11 @@
"properties": {
"id": { "type": "integer" },
"title": { "type": "string" },
- "description": { "type": "string" },
+ "description": { "type": ["string", "null"] },
"state": { "type": "string" },
"iid": { "type": "integer" },
"created_at": { "type": "date" },
- "due_date": { "type": "date" },
+ "due_date": { "type": ["date", "null"] },
"issues": {
"type": "array",
"items": { "$ref": "issue.json" }
diff --git a/spec/fixtures/api/schemas/evidences/project.json b/spec/fixtures/api/schemas/evidences/project.json
index 542686542f8..3a094bd276f 100644
--- a/spec/fixtures/api/schemas/evidences/project.json
+++ b/spec/fixtures/api/schemas/evidences/project.json
@@ -9,7 +9,7 @@
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
- "description": { "type": "string" },
+ "description": { "type": ["string", "null"] },
"created_at": { "type": "date" }
},
"additionalProperties": false
diff --git a/spec/fixtures/api/schemas/evidences/release.json b/spec/fixtures/api/schemas/evidences/release.json
index 68c872a9dc8..37eb9a9b5c0 100644
--- a/spec/fixtures/api/schemas/evidences/release.json
+++ b/spec/fixtures/api/schemas/evidences/release.json
@@ -2,7 +2,7 @@
"type": "object",
"required": [
"id",
- "tag",
+ "tag_name",
"name",
"description",
"created_at",
@@ -11,8 +11,8 @@
],
"properties": {
"id": { "type": "integer" },
- "tag": { "type": "string" },
- "name": { "type": "string" },
+ "tag_name": { "type": "string" },
+ "name": { "type": ["string", "null"] },
"description": { "type": "string" },
"created_at": { "type": "date" },
"project": { "$ref": "project.json" },
diff --git a/spec/fixtures/api/schemas/job/build_trace.json b/spec/fixtures/api/schemas/job/build_trace.json
new file mode 100644
index 00000000000..becd881ea57
--- /dev/null
+++ b/spec/fixtures/api/schemas/job/build_trace.json
@@ -0,0 +1,31 @@
+{
+ "description": "Build trace",
+ "type": "object",
+ "required": [
+ "id",
+ "status",
+ "complete",
+ "state",
+ "append",
+ "truncated",
+ "offset",
+ "size",
+ "total"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "status": { "type": "string" },
+ "complete": { "type": "boolean" },
+ "state": { "type": ["string", "null"] },
+ "append": { "type": ["boolean", "null"] },
+ "truncated": { "type": ["boolean", "null"] },
+ "offset": { "type": ["integer", "null"] },
+ "size": { "type": ["integer", "null"] },
+ "total": { "type": ["integer", "null"] },
+ "html": { "type": ["string", "null"] },
+ "lines": {
+ "type": ["array", "null"],
+ "items": { "$ref": "./build_trace_line.json" }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/job/build_trace_line.json b/spec/fixtures/api/schemas/job/build_trace_line.json
new file mode 100644
index 00000000000..18726dff2bb
--- /dev/null
+++ b/spec/fixtures/api/schemas/job/build_trace_line.json
@@ -0,0 +1,18 @@
+{
+ "description": "Build trace line",
+ "type": "object",
+ "required": [
+ "offset",
+ "content"
+ ],
+ "properties": {
+ "offset": { "type": "integer" },
+ "content": {
+ "type": "array",
+ "items": { "$ref": "./build_trace_line_content.json" }
+ },
+ "section": "string",
+ "section_header": "boolean",
+ "section_duration": "string"
+ }
+}
diff --git a/spec/fixtures/api/schemas/job/build_trace_line_content.json b/spec/fixtures/api/schemas/job/build_trace_line_content.json
new file mode 100644
index 00000000000..41f8124c113
--- /dev/null
+++ b/spec/fixtures/api/schemas/job/build_trace_line_content.json
@@ -0,0 +1,11 @@
+{
+ "description": "Build trace line content",
+ "type": "object",
+ "required": [
+ "text"
+ ],
+ "properties": {
+ "text": { "type": "string" },
+ "style": { "type": "string" }
+ }
+}
diff --git a/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb
new file mode 100644
index 00000000000..897b8f4e9ef
--- /dev/null
+++ b/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Mutations::ResolvesGroup do
+ let(:mutation_class) do
+ Class.new(Mutations::BaseMutation) do
+ include Mutations::ResolvesGroup
+ end
+ end
+
+ let(:context) { double }
+ subject(:mutation) { mutation_class.new(object: nil, context: context) }
+
+ it 'uses the GroupsResolver to resolve groups by path' do
+ group = create(:group)
+
+ expect(Resolvers::GroupResolver).to receive(:new).with(object: nil, context: context).and_call_original
+ expect(mutation.resolve_group(full_path: group.full_path).sync).to eq(group)
+ end
+end
diff --git a/spec/graphql/resolvers/todo_resolver_spec.rb b/spec/graphql/resolvers/todo_resolver_spec.rb
new file mode 100644
index 00000000000..fef761d7243
--- /dev/null
+++ b/spec/graphql/resolvers/todo_resolver_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::TodoResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:author1) { create(:user) }
+ let_it_be(:author2) { create(:user) }
+
+ let_it_be(:todo1) { create(:todo, user: user, target_type: 'MergeRequest', state: :pending, action: Todo::MENTIONED, author: author1) }
+ let_it_be(:todo2) { create(:todo, user: user, state: :done, action: Todo::ASSIGNED, author: author2) }
+ let_it_be(:todo3) { create(:todo, user: user, state: :pending, action: Todo::ASSIGNED, author: author1) }
+
+ it 'calls TodosFinder' do
+ expect_next_instance_of(TodosFinder) do |finder|
+ expect(finder).to receive(:execute)
+ end
+
+ resolve_todos
+ end
+
+ context 'when using no filter' do
+ it 'returns expected todos' do
+ todos = resolve(described_class, obj: user, args: {}, ctx: { current_user: user })
+
+ expect(todos).to contain_exactly(todo1, todo3)
+ end
+ end
+
+ context 'when using filters' do
+ # TODO These can be removed as soon as we support filtering for multiple field contents for todos
+
+ it 'just uses the first state' do
+ todos = resolve(described_class, obj: user, args: { state: [:done, :pending] }, ctx: { current_user: user })
+
+ expect(todos).to contain_exactly(todo2)
+ end
+
+ it 'just uses the first action' do
+ todos = resolve(described_class, obj: user, args: { action: [Todo::MENTIONED, Todo::ASSIGNED] }, ctx: { current_user: user })
+
+ expect(todos).to contain_exactly(todo1)
+ end
+
+ it 'just uses the first author id' do
+ # We need a pending todo for now because of TodosFinder's state query
+ todo4 = create(:todo, user: user, state: :pending, action: Todo::ASSIGNED, author: author2)
+
+ todos = resolve(described_class, obj: user, args: { author_id: [author2.id, author1.id] }, ctx: { current_user: user })
+
+ expect(todos).to contain_exactly(todo4)
+ end
+
+ it 'just uses the first project id' do
+ project1 = create(:project)
+ project2 = create(:project)
+
+ create(:todo, project: project1, user: user, state: :pending, action: Todo::ASSIGNED, author: author1)
+ todo5 = create(:todo, project: project2, user: user, state: :pending, action: Todo::ASSIGNED, author: author1)
+
+ todos = resolve(described_class, obj: user, args: { project_id: [project2.id, project1.id] }, ctx: { current_user: user })
+
+ expect(todos).to contain_exactly(todo5)
+ end
+
+ it 'just uses the first group id' do
+ group1 = create(:group)
+ group2 = create(:group)
+
+ group1.add_developer(user)
+ group2.add_developer(user)
+
+ create(:todo, group: group1, user: user, state: :pending, action: Todo::ASSIGNED, author: author1)
+ todo5 = create(:todo, group: group2, user: user, state: :pending, action: Todo::ASSIGNED, author: author1)
+
+ todos = resolve(described_class, obj: user, args: { group_id: [group2.id, group1.id] }, ctx: { current_user: user })
+
+ expect(todos).to contain_exactly(todo5)
+ end
+
+ it 'just uses the first target' do
+ todos = resolve(described_class, obj: user, args: { type: %w[Issue MergeRequest] }, ctx: { current_user: user })
+
+ # Just todo3 because todo2 is in state "done"
+ expect(todos).to contain_exactly(todo3)
+ end
+ end
+
+ context 'when no user is provided' do
+ it 'returns no todos' do
+ todos = resolve(described_class, obj: nil, args: {}, ctx: { current_user: current_user })
+
+ expect(todos).to be_empty
+ end
+ end
+
+ context 'when provided user is not current user' do
+ it 'returns no todos' do
+ todos = resolve(described_class, obj: user, args: {}, ctx: { current_user: current_user })
+
+ expect(todos).to be_empty
+ end
+ end
+ end
+
+ def resolve_todos(args = {}, context = { current_user: current_user })
+ resolve(described_class, obj: current_user, args: args, ctx: context)
+ end
+end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index 784a4f4b4c9..1365bc0dc14 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -7,7 +7,7 @@ describe GitlabSchema.types['Query'] do
expect(described_class.graphql_name).to eq('Query')
end
- it { is_expected.to have_graphql_fields(:project, :namespace, :group, :echo, :metadata) }
+ it { is_expected.to have_graphql_fields(:project, :namespace, :group, :echo, :metadata, :current_user) }
describe 'namespace field' do
subject { described_class.fields['namespace'] }
diff --git a/spec/graphql/types/todo_type_spec.rb b/spec/graphql/types/todo_type_spec.rb
new file mode 100644
index 00000000000..a5ea5bcffb0
--- /dev/null
+++ b/spec/graphql/types/todo_type_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['Todo'] do
+ it 'has the correct fields' do
+ expected_fields = [:id, :project, :group, :author, :action, :target_type, :body, :state, :created_at]
+
+ is_expected.to have_graphql_fields(*expected_fields)
+ end
+
+ it { expect(described_class).to require_graphql_authorizations(:read_todo) }
+end
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index dd5f2f97ac9..1baea13299b 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -248,60 +248,6 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
end
end
- describe '#html_with_state' do
- shared_examples_for 'html_with_states' do
- it 'returns html content with state' do
- result = stream.html_with_state
-
- expect(result.html).to eq("<span>1234</span>")
- end
-
- context 'follow-up state' do
- let!(:last_result) { stream.html_with_state }
-
- before do
- data_stream.seek(4, IO::SEEK_SET)
- data_stream.write("5678")
- stream.seek(0)
- end
-
- it "returns appended trace" do
- result = stream.html_with_state(last_result.state)
-
- expect(result.append).to be_truthy
- expect(result.html).to eq("<span>5678</span>")
- end
- end
- end
-
- context 'when stream is StringIO' do
- let(:data_stream) do
- StringIO.new("1234")
- end
-
- let(:stream) do
- described_class.new { data_stream }
- end
-
- it_behaves_like 'html_with_states'
- end
-
- context 'when stream is ChunkedIO' do
- let(:data_stream) do
- Gitlab::Ci::Trace::ChunkedIO.new(build).tap do |chunked_io|
- chunked_io.write("1234")
- chunked_io.seek(0, IO::SEEK_SET)
- end
- end
-
- let(:stream) do
- described_class.new { data_stream }
- end
-
- it_behaves_like 'html_with_states'
- end
- end
-
describe '#html' do
shared_examples_for 'htmls' do
it "returns html" do
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index ba6abba4e61..71489adb373 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -252,31 +252,6 @@ describe Gitlab::GitalyClient::CommitService do
end
end
- describe '#patch' do
- let(:request) do
- Gitaly::CommitPatchRequest.new(
- repository: repository_message, revision: revision
- )
- end
- let(:response) { [double(data: "my "), double(data: "diff")] }
-
- subject { described_class.new(repository).patch(revision) }
-
- it 'sends an RPC request' do
- expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_patch)
- .with(request, kind_of(Hash)).and_return([])
-
- subject
- end
-
- it 'concatenates the responses data' do
- allow_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_patch)
- .with(request, kind_of(Hash)).and_return(response)
-
- expect(subject).to eq("my diff")
- end
- end
-
describe '#commit_stats' do
let(:request) do
Gitaly::CommitStatsRequest.new(
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 187a8a37179..1efd7bf5c71 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -27,6 +27,7 @@ issues:
- design_versions
- prometheus_alerts
- prometheus_alert_events
+- self_managed_prometheus_alert_events
events:
- author
- project
@@ -81,6 +82,7 @@ releases:
- links
- milestone_releases
- milestones
+- evidence
links:
- release
project_members:
@@ -400,6 +402,7 @@ project:
- operations_feature_flags_client
- prometheus_alerts
- prometheus_alert_events
+- self_managed_prometheus_alert_events
- software_license_policies
- project_registry
- packages
@@ -473,6 +476,8 @@ prometheus_alerts:
- prometheus_alert_events
prometheus_alert_events:
- project
+self_managed_prometheus_alert_events:
+- project
epic_issues:
- issue
- epic
@@ -506,6 +511,8 @@ lists:
milestone_releases:
- milestone
- release
+evidences:
+- release
design: &design
- issue
- actions
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index ebc5d9d1f56..8ae571a69ef 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -127,6 +127,12 @@ Release:
- created_at
- updated_at
- released_at
+Evidence:
+- id
+- release_id
+- summary
+- created_at
+- updated_at
Releases::Link:
- id
- release_id
diff --git a/spec/models/ci/build_trace_spec.rb b/spec/models/ci/build_trace_spec.rb
new file mode 100644
index 00000000000..2471a6fa827
--- /dev/null
+++ b/spec/models/ci/build_trace_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::BuildTrace do
+ let(:build) { build_stubbed(:ci_build) }
+ let(:state) { nil }
+ let(:data) { StringIO.new('the-stream') }
+
+ let(:stream) do
+ Gitlab::Ci::Trace::Stream.new { data }
+ end
+
+ subject { described_class.new(build: build, stream: stream, state: state, content_format: content_format) }
+
+ shared_examples 'delegates methods' do
+ it { is_expected.to delegate_method(:state).to(:trace) }
+ it { is_expected.to delegate_method(:append).to(:trace) }
+ it { is_expected.to delegate_method(:truncated).to(:trace) }
+ it { is_expected.to delegate_method(:offset).to(:trace) }
+ it { is_expected.to delegate_method(:size).to(:trace) }
+ it { is_expected.to delegate_method(:total).to(:trace) }
+ it { is_expected.to delegate_method(:id).to(:build).with_prefix }
+ it { is_expected.to delegate_method(:status).to(:build).with_prefix }
+ it { is_expected.to delegate_method(:complete?).to(:build).with_prefix }
+ end
+
+ context 'with :json content format' do
+ let(:content_format) { :json }
+
+ it_behaves_like 'delegates methods'
+
+ it { is_expected.to be_json }
+
+ it 'returns formatted trace' do
+ expect(subject.trace.lines).to eq([
+ { offset: 0, content: [{ text: 'the-stream' }] }
+ ])
+ end
+ end
+
+ context 'with :html content format' do
+ let(:content_format) { :html }
+
+ it_behaves_like 'delegates methods'
+
+ it { is_expected.to be_html }
+
+ it 'returns formatted trace' do
+ expect(subject.trace.html).to eq('<span>the-stream</span>')
+ end
+ end
+end
diff --git a/spec/models/evidence_spec.rb b/spec/models/evidence_spec.rb
new file mode 100644
index 00000000000..00788c2c391
--- /dev/null
+++ b/spec/models/evidence_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Evidence do
+ let_it_be(:project) { create(:project) }
+ let(:release) { create(:release, project: project) }
+ let(:schema_file) { 'evidences/evidence' }
+ let(:summary_json) { described_class.last.summary.to_json }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:release) }
+ end
+
+ describe 'summary_sha' do
+ it 'returns nil if summary is nil' do
+ expect(build(:evidence, summary: nil).summary_sha).to be_nil
+ end
+ end
+
+ describe '#generate_summary_and_sha' do
+ before do
+ described_class.create!(release: release)
+ end
+
+ context 'when a release name is not provided' do
+ let(:release) { create(:release, project: project, name: nil) }
+
+ it 'creates a valid JSON object' do
+ expect(release.name).to be_nil
+ expect(summary_json).to match_schema(schema_file)
+ end
+ end
+
+ context 'when a release is associated to a milestone' do
+ let(:milestone) { create(:milestone, project: project) }
+ let(:release) { create(:release, project: project, milestones: [milestone]) }
+
+ context 'when a milestone has no issue associated with it' do
+ it 'creates a valid JSON object' do
+ expect(milestone.issues).to be_empty
+ expect(summary_json).to match_schema(schema_file)
+ end
+ end
+
+ context 'when a milestone has no description' do
+ let(:milestone) { create(:milestone, project: project, description: nil) }
+
+ it 'creates a valid JSON object' do
+ expect(milestone.description).to be_nil
+ expect(summary_json).to match_schema(schema_file)
+ end
+ end
+
+ context 'when a milestone has no due_date' do
+ let(:milestone) { create(:milestone, project: project, due_date: nil) }
+
+ it 'creates a valid JSON object' do
+ expect(milestone.due_date).to be_nil
+ expect(summary_json).to match_schema(schema_file)
+ end
+ end
+
+ context 'when a milestone has an issue' do
+ context 'when the issue has no description' do
+ let(:issue) { create(:issue, project: project, description: nil, state: 'closed') }
+
+ before do
+ milestone.issues << issue
+ end
+
+ it 'creates a valid JSON object' do
+ expect(milestone.issues.first.description).to be_nil
+ expect(summary_json).to match_schema(schema_file)
+ end
+ end
+ end
+ end
+
+ context 'when a release is not associated to any milestone' do
+ it 'creates a valid JSON object' do
+ expect(release.milestones).to be_empty
+ expect(summary_json).to match_schema(schema_file)
+ end
+ end
+ end
+end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 892c31a9204..520421ac5e3 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1042,4 +1042,21 @@ describe Group do
expect(group.access_request_approvers_to_be_notified).to eq(active_owners_in_recent_sign_in_desc_order)
end
end
+
+ describe '.groups_including_descendants_by' do
+ it 'returns the expected groups for a group and its descendants' do
+ parent_group1 = create(:group)
+ child_group1 = create(:group, parent: parent_group1)
+ child_group2 = create(:group, parent: parent_group1)
+
+ parent_group2 = create(:group)
+ child_group3 = create(:group, parent: parent_group2)
+
+ create(:group)
+
+ groups = described_class.groups_including_descendants_by([parent_group2.id, parent_group1.id])
+
+ expect(groups).to contain_exactly(parent_group1, parent_group2, child_group1, child_group2, child_group3)
+ end
+ end
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index fe08dc4f5e6..025c11d6407 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -6,7 +6,7 @@ describe WebHook do
let(:hook) { build(:project_hook) }
describe 'associations' do
- it { is_expected.to have_many(:web_hook_logs).dependent(:destroy) }
+ it { is_expected.to have_many(:web_hook_logs) }
end
describe 'validations' do
@@ -85,4 +85,13 @@ describe WebHook do
hook.async_execute(data, hook_name)
end
end
+
+ describe '#destroy' do
+ it 'cascades to web_hook_logs' do
+ web_hook = create(:project_hook)
+ create_list(:web_hook_log, 3, web_hook: web_hook)
+
+ expect { web_hook.destroy }.to change(web_hook.web_hook_logs, :count).by(-3)
+ end
+ end
end
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index e7a8d27a036..64799421eb6 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -15,11 +15,13 @@ RSpec.describe Release do
it { is_expected.to have_many(:links).class_name('Releases::Link') }
it { is_expected.to have_many(:milestones) }
it { is_expected.to have_many(:milestone_releases) }
+ it { is_expected.to have_one(:evidence) }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:description) }
+ it { is_expected.to validate_presence_of(:tag) }
context 'when a release exists in the database without a name' do
it 'does not require name' do
@@ -89,4 +91,22 @@ RSpec.describe Release do
end
end
end
+
+ describe 'evidence' do
+ describe '#create_evidence!' do
+ context 'when a release is created' do
+ it 'creates one Evidence object too' do
+ expect { release }.to change(Evidence, :count).by(1)
+ end
+ end
+ end
+
+ context 'when a release is deleted' do
+ it 'also deletes the associated evidence' do
+ release = create(:release)
+
+ expect { release.destroy }.to change(Evidence, :count).by(-1)
+ end
+ end
+ end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index c2566ccd047..487a1c619c6 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -253,14 +253,14 @@ describe Todo do
end
end
- describe '.for_group_and_descendants' do
+ describe '.for_group_ids_and_descendants' do
it 'returns the todos for a group and its descendants' do
parent_group = create(:group)
child_group = create(:group, parent: parent_group)
todo1 = create(:todo, group: parent_group)
todo2 = create(:todo, group: child_group)
- todos = described_class.for_group_and_descendants(parent_group)
+ todos = described_class.for_group_ids_and_descendants([parent_group.id])
expect(todos).to contain_exactly(todo1, todo2)
end
diff --git a/spec/policies/todo_policy_spec.rb b/spec/policies/todo_policy_spec.rb
new file mode 100644
index 00000000000..be6fecd1045
--- /dev/null
+++ b/spec/policies/todo_policy_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe TodoPolicy do
+ let_it_be(:author) { create(:user) }
+
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+ let_it_be(:user3) { create(:user) }
+
+ let_it_be(:todo1) { create(:todo, author: author, user: user1) }
+ let_it_be(:todo2) { create(:todo, author: author, user: user2) }
+ let_it_be(:todo3) { create(:todo, author: author, user: user2) }
+ let_it_be(:todo4) { create(:todo, author: author, user: user3) }
+
+ def permissions(user, todo)
+ described_class.new(user, todo)
+ end
+
+ describe 'own_todo' do
+ it 'allows owners to access their own todos' do
+ [
+ [user1, todo1],
+ [user2, todo2],
+ [user2, todo3],
+ [user3, todo4]
+ ].each do |user, todo|
+ expect(permissions(user, todo)).to be_allowed(:read_todo)
+ end
+ end
+
+ it 'does not allow users to access todos of other users' do
+ [
+ [user1, todo2],
+ [user1, todo3],
+ [user2, todo1],
+ [user2, todo4],
+ [user3, todo1],
+ [user3, todo2],
+ [user3, todo3]
+ ].each do |user, todo|
+ expect(permissions(user, todo)).to be_disallowed(:read_todo)
+ end
+ end
+ end
+end
diff --git a/spec/serializers/build_trace_entity_spec.rb b/spec/serializers/build_trace_entity_spec.rb
new file mode 100644
index 00000000000..bafead04a51
--- /dev/null
+++ b/spec/serializers/build_trace_entity_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe BuildTraceEntity do
+ let(:build) { build_stubbed(:ci_build) }
+ let(:request) { double('request') }
+
+ let(:stream) do
+ Gitlab::Ci::Trace::Stream.new do
+ StringIO.new('the-trace')
+ end
+ end
+
+ let(:build_trace) do
+ Ci::BuildTrace.new(build: build, stream: stream, content_format: content_format, state: nil)
+ end
+
+ let(:entity) do
+ described_class.new(build_trace, request: request)
+ end
+
+ subject { entity.as_json }
+
+ shared_examples 'includes build and trace metadata' do
+ it 'includes build attributes' do
+ expect(subject[:id]).to eq(build.id)
+ expect(subject[:status]).to eq(build.status)
+ expect(subject[:complete]).to eq(build.complete?)
+ end
+
+ it 'includes trace metadata' do
+ expect(subject).to include(:state)
+ expect(subject).to include(:append)
+ expect(subject).to include(:truncated)
+ expect(subject).to include(:offset)
+ expect(subject).to include(:size)
+ expect(subject).to include(:total)
+ end
+ end
+
+ context 'when content format is :json' do
+ let(:content_format) { :json }
+
+ it_behaves_like 'includes build and trace metadata'
+
+ it 'includes the trace content in json' do
+ expect(subject[:lines]).to eq([
+ { offset: 0, content: [{ text: 'the-trace' }] }
+ ])
+ end
+ end
+
+ context 'when content format is :html' do
+ let(:content_format) { :html }
+
+ it_behaves_like 'includes build and trace metadata'
+
+ it 'includes the trace content in json' do
+ expect(subject[:html]).to eq('<span>the-trace</span>')
+ end
+ end
+end
diff --git a/spec/serializers/evidences/author_entity_spec.rb b/spec/serializers/evidences/author_entity_spec.rb
deleted file mode 100644
index 1d0fa95217c..00000000000
--- a/spec/serializers/evidences/author_entity_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Evidences::AuthorEntity do
- let(:entity) { described_class.new(build(:author)) }
-
- subject { entity.as_json }
-
- it 'exposes the expected fields' do
- expect(subject.keys).to contain_exactly(:id, :name, :email)
- end
-end
diff --git a/spec/serializers/evidences/evidence_entity_spec.rb b/spec/serializers/evidences/evidence_entity_spec.rb
new file mode 100644
index 00000000000..531708e3be6
--- /dev/null
+++ b/spec/serializers/evidences/evidence_entity_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Evidences::EvidenceEntity do
+ let(:evidence) { build(:evidence) }
+ let(:entity) { described_class.new(evidence) }
+
+ subject { entity.as_json }
+
+ it 'exposes the expected fields' do
+ expect(subject.keys).to contain_exactly(:release)
+ end
+end
diff --git a/spec/serializers/evidences/evidence_serializer_spec.rb b/spec/serializers/evidences/evidence_serializer_spec.rb
new file mode 100644
index 00000000000..5322f6a43fc
--- /dev/null
+++ b/spec/serializers/evidences/evidence_serializer_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Evidences::EvidenceSerializer do
+ it 'represents an EvidenceEntity entity' do
+ expect(described_class.entity_class).to eq(Evidences::EvidenceEntity)
+ end
+end
diff --git a/spec/serializers/evidences/issue_entity_spec.rb b/spec/serializers/evidences/issue_entity_spec.rb
index a1402808757..915df986887 100644
--- a/spec/serializers/evidences/issue_entity_spec.rb
+++ b/spec/serializers/evidences/issue_entity_spec.rb
@@ -8,6 +8,6 @@ describe Evidences::IssueEntity do
subject { entity.as_json }
it 'exposes the expected fields' do
- expect(subject.keys).to contain_exactly(:id, :title, :description, :author, :state, :iid, :confidential, :created_at, :due_date)
+ expect(subject.keys).to contain_exactly(:id, :title, :description, :state, :iid, :confidential, :created_at, :due_date)
end
end
diff --git a/spec/serializers/evidences/milestone_entity_spec.rb b/spec/serializers/evidences/milestone_entity_spec.rb
index 082e178618e..68eb12093da 100644
--- a/spec/serializers/evidences/milestone_entity_spec.rb
+++ b/spec/serializers/evidences/milestone_entity_spec.rb
@@ -12,7 +12,7 @@ describe Evidences::MilestoneEntity do
expect(subject.keys).to contain_exactly(:id, :title, :description, :state, :iid, :created_at, :due_date, :issues)
end
- context 'when there issues linked to this milestone' do
+ context 'when there are issues linked to this milestone' do
let(:issue_1) { build(:issue) }
let(:issue_2) { build(:issue) }
let(:milestone) { build(:milestone, issues: [issue_1, issue_2]) }
diff --git a/spec/support/shared_examples/evidence_updated_exposed_fields.rb b/spec/support/shared_examples/evidence_updated_exposed_fields.rb
new file mode 100644
index 00000000000..2a02fdd7666
--- /dev/null
+++ b/spec/support/shared_examples/evidence_updated_exposed_fields.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+shared_examples 'updated exposed field' do
+ it 'creates another Evidence object' do
+ model.send("#{updated_field}=", updated_value)
+
+ expect(model.evidence_summary_keys).to include(updated_field)
+ expect { model.save! }.to change(Evidence, :count).by(1)
+ expect(updated_json_field).to eq(updated_value)
+ end
+end
+
+shared_examples 'updated non-exposed field' do
+ it 'does not create any Evidence object' do
+ model.send("#{updated_field}=", updated_value)
+
+ expect(model.evidence_summary_keys).not_to include(updated_field)
+ expect { model.save! }.not_to change(Evidence, :count)
+ end
+end
+
+shared_examples 'updated field on non-linked entity' do
+ it 'does not create any Evidence object' do
+ model.send("#{updated_field}=", updated_value)
+
+ expect(model.evidence_summary_keys).to be_empty
+ expect { model.save! }.not_to change(Evidence, :count)
+ end
+end
diff --git a/spec/workers/create_evidence_worker_spec.rb b/spec/workers/create_evidence_worker_spec.rb
new file mode 100644
index 00000000000..364b2098251
--- /dev/null
+++ b/spec/workers/create_evidence_worker_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe CreateEvidenceWorker do
+ let!(:release) { create(:release) }
+
+ it 'creates a new Evidence' do
+ expect { described_class.new.perform(release.id) }.to change(Evidence, :count).by(1)
+ end
+end