Commit edafda79 authored by Felipe Artur's avatar Felipe Artur

Allow to create test cases on GraphQL

Add GraphQL mutation to create test cases
parent 30c193a0
...@@ -3004,6 +3004,56 @@ type CreateSnippetPayload { ...@@ -3004,6 +3004,56 @@ type CreateSnippetPayload {
snippet: Snippet snippet: Snippet
} }
"""
Autogenerated input type of CreateTestCase
"""
input CreateTestCaseInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The test case description
"""
description: String
"""
The IDs of labels to be added to the test case.
"""
labelIds: [ID!]
"""
The project full path to create the test case
"""
projectPath: ID!
"""
The test case title
"""
title: String!
}
"""
Autogenerated return type of CreateTestCase
"""
type CreateTestCasePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The test case created
"""
testCase: Issue
}
interface CurrentUserTodos { interface CurrentUserTodos {
""" """
Todos for the current user Todos for the current user
...@@ -10672,6 +10722,7 @@ type Mutation { ...@@ -10672,6 +10722,7 @@ type Mutation {
createNote(input: CreateNoteInput!): CreateNotePayload createNote(input: CreateNoteInput!): CreateNotePayload
createRequirement(input: CreateRequirementInput!): CreateRequirementPayload createRequirement(input: CreateRequirementInput!): CreateRequirementPayload
createSnippet(input: CreateSnippetInput!): CreateSnippetPayload createSnippet(input: CreateSnippetInput!): CreateSnippetPayload
createTestCase(input: CreateTestCaseInput!): CreateTestCasePayload
dastOnDemandScanCreate(input: DastOnDemandScanCreateInput!): DastOnDemandScanCreatePayload dastOnDemandScanCreate(input: DastOnDemandScanCreateInput!): DastOnDemandScanCreatePayload
dastScannerProfileCreate(input: DastScannerProfileCreateInput!): DastScannerProfileCreatePayload dastScannerProfileCreate(input: DastScannerProfileCreateInput!): DastScannerProfileCreatePayload
dastScannerProfileDelete(input: DastScannerProfileDeleteInput!): DastScannerProfileDeletePayload dastScannerProfileDelete(input: DastScannerProfileDeleteInput!): DastScannerProfileDeletePayload
......
...@@ -8150,6 +8150,150 @@ ...@@ -8150,6 +8150,150 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "INPUT_OBJECT",
"name": "CreateTestCaseInput",
"description": "Autogenerated input type of CreateTestCase",
"fields": null,
"inputFields": [
{
"name": "title",
"description": "The test case title",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "description",
"description": "The test case description",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "labelIds",
"description": "The IDs of labels to be added to the test case.",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "projectPath",
"description": "The project full path to create the test case",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CreateTestCasePayload",
"description": "Autogenerated return type of CreateTestCase",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "testCase",
"description": "The test case created",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "INTERFACE", "kind": "INTERFACE",
"name": "CurrentUserTodos", "name": "CurrentUserTodos",
...@@ -30360,6 +30504,33 @@ ...@@ -30360,6 +30504,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "createTestCase",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "CreateTestCaseInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "CreateTestCasePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "dastOnDemandScanCreate", "name": "dastOnDemandScanCreate",
"description": null, "description": null,
...@@ -511,6 +511,16 @@ Autogenerated return type of CreateSnippet ...@@ -511,6 +511,16 @@ Autogenerated return type of CreateSnippet
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `snippet` | Snippet | The snippet after mutation | | `snippet` | Snippet | The snippet after mutation |
### CreateTestCasePayload
Autogenerated return type of CreateTestCase
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `testCase` | Issue | The test case created |
### DastOnDemandScanCreatePayload ### DastOnDemandScanCreatePayload
Autogenerated return type of DastOnDemandScanCreate Autogenerated return type of DastOnDemandScanCreate
......
...@@ -37,6 +37,7 @@ module EE ...@@ -37,6 +37,7 @@ module EE
mount_mutation ::Mutations::DastScannerProfiles::Delete mount_mutation ::Mutations::DastScannerProfiles::Delete
mount_mutation ::Mutations::Security::CiConfiguration::ConfigureSast mount_mutation ::Mutations::Security::CiConfiguration::ConfigureSast
mount_mutation ::Mutations::Namespaces::IncreaseStorageTemporarily mount_mutation ::Mutations::Namespaces::IncreaseStorageTemporarily
mount_mutation ::Mutations::QualityManagement::TestCases::Create
end end
end end
end end
......
# frozen_string_literal: true
module Mutations
module QualityManagement
module TestCases
class Create < BaseMutation
include ResolvesProject
graphql_name 'CreateTestCase'
authorize :admin_issue
argument :title, GraphQL::STRING_TYPE,
required: true,
description: 'The test case title'
argument :description, GraphQL::STRING_TYPE,
required: false,
description: 'The test case description'
argument :label_ids,
[GraphQL::ID_TYPE],
required: false,
description: 'The IDs of labels to be added to the test case.'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'The project full path to create the test case'
field :test_case, Types::IssueType,
null: true,
description: 'The test case created'
def resolve(args)
project_path = args.delete(:project_path)
project = authorized_find!(full_path: project_path)
result = ::QualityManagement::TestCases::CreateService.new(
project,
context[:current_user],
**args
).execute
test_case = result.payload[:issue]
{
test_case: test_case&.persisted? ? test_case : nil,
errors: Array.wrap(result.message)
}
end
private
def find_object(full_path:)
resolve_project(full_path: full_path)
end
end
end
end
end
...@@ -5,7 +5,7 @@ module QualityManagement ...@@ -5,7 +5,7 @@ module QualityManagement
class CreateService < BaseService class CreateService < BaseService
ISSUE_TYPE = 'test_case' ISSUE_TYPE = 'test_case'
def initialize(project, current_user, title:, description:, label_ids: []) def initialize(project, current_user, title:, description: nil, label_ids: [])
super(project, current_user) super(project, current_user)
@title = title @title = title
......
---
title: Add GraphQL mutation to create test cases
merge_request: 41190
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Create test case' do
include GraphqlHelpers
let_it_be_with_refind(:project) { create(:project, :private) }
let_it_be(:current_user) { create(:user) }
let_it_be(:label) { create(:label, project: project) }
let(:variables) { { project_path: project.full_path, title: 'foo', description: 'bar', label_ids: [label.id] } }
let(:mutation) do
graphql_mutation(:create_test_case, variables) do
<<~QL
clientMutationId
errors
testCase {
title
description
labels {
edges {
node {
id
}
}
}
}
QL
end
end
def mutation_response
graphql_mutation_response(:create_test_case)
end
describe '#resolve' do
context 'when quality management feature is not available' do
before do
stub_licensed_features(quality_management: false)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [
'The resource that you are attempting to access does not exist '\
'or you don\'t have permission to perform this action'
]
context 'with authorized logged user', :aggregate_failures do
before_all do
project.add_reporter(current_user)
end
it_behaves_like 'a mutation that returns errors in the response', errors: ["Test cases are not available for this project"]
end
end
context 'when quality management feature is available' do
before do
stub_licensed_features(quality_management: true)
end
context 'when user can create test cases' do
before_all do
project.add_reporter(current_user)
end
it 'creates new test case', :aggregate_failures do
expect { post_graphql_mutation(mutation, current_user: current_user) }.to change { Issue.count }.by(1)
test_case = mutation_response['testCase']
expect(test_case).not_to be_nil
expect(test_case['title']).to eq('foo')
expect(test_case['description']).to eq('bar')
expect(test_case['labels']['edges'][0]["node"]["id"]).to eq(label.to_global_id.to_s)
expect(mutation_response['errors']).to eq([])
end
context 'with invalid arguments' do
let(:variables) { { not_valid: true } }
it_behaves_like 'an invalid argument to the mutation', argument_name: :not_valid
end
context 'when quality_test_cases flag is disabled' do
before do
stub_feature_flags(quality_test_cases: false)
end
it_behaves_like 'a mutation that returns errors in the response', errors: ["Test cases are not available for this project"]
end
end
context 'when user cannot create test cases' do
before_all do
project.add_guest(current_user)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: [
'The resource that you are attempting to access does not exist '\
'or you don\'t have permission to perform this action'
]
end
end
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