Repository: ctreminiom/go-atlassian
Pull request #142 (2022-09-15 07:53:05)

Feature/attachment field


Patch:
diff --git a/jira/internal/attachment_impl.go b/jira/internal/attachment_impl.go
index be5199a..ee4602b 100644
--- a/jira/internal/attachment_impl.go
+++ b/jira/internal/attachment_impl.go
@@ -10,6 +10,8 @@ import (
 	"io"
 	"mime/multipart"
 	"net/http"
+	"net/url"
+	"strings"
 )
 
 func NewIssueAttachmentService(client service.Client, version string) (*IssueAttachmentService, error) {
@@ -76,11 +78,47 @@ func (i *IssueAttachmentService) Add(ctx context.Context, issueKeyOrId, fileName
 	return i.internalClient.Add(ctx, issueKeyOrId, fileName, file)
 }
 
+// Download returns the contents of an attachment. A Range header can be set to define a range of bytes within the attachment to download.
+//
+// See the HTTP Range header standard for details.
+//
+// GET /rest/api/{2-3}/attachment/content/{id}
+//
+// https://docs.go-atlassian.io/jira-software-cloud/issues/attachments#download-attachment
+func (i *IssueAttachmentService) Download(ctx context.Context, attachmentID string, redirect bool) (*model.ResponseScheme, error) {
+	return i.internalClient.Download(ctx, attachmentID, redirect)
+}
+
 type internalIssueAttachmentServiceImpl struct {
 	c       service.Client
 	version string
 }
 
+func (i *internalIssueAttachmentServiceImpl) Download(ctx context.Context, attachmentID string, redirect bool) (*model.ResponseScheme, error) {
+
+	if attachmentID == "" {
+		return nil, model.ErrNoAttachmentIDError
+	}
+
+	var endpoint strings.Builder
+	endpoint.WriteString(fmt.Sprintf("rest/api/%v/attachment/content/%v", i.version, attachmentID))
+
+	if !redirect {
+
+		params := url.Values{}
+		params.Add("redirect", "false") //default: true
+
+		endpoint.WriteString(fmt.Sprintf("?%v", params.Encode()))
+	}
+
+	request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint.String(), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return i.c.Call(request, nil)
+}
+
 func (i *internalIssueAttachmentServiceImpl) Settings(ctx context.Context) (*model.AttachmentSettingScheme, *model.ResponseScheme, error) {
 
 	endpoint := fmt.Sprintf("rest/api/%v/attachment/meta", i.version)
diff --git a/jira/internal/field_impl.go b/jira/internal/field_impl.go
index 19ff670..3511912 100644
--- a/jira/internal/field_impl.go
+++ b/jira/internal/field_impl.go
@@ -12,7 +12,8 @@ import (
 	"strings"
 )
 
-func NewIssueFieldService(client service.Client, version string, configuration *IssueFieldConfigService, context *IssueFieldContextService) (*IssueFieldService, error) {
+func NewIssueFieldService(client service.Client, version string, configuration *IssueFieldConfigService, context *IssueFieldContextService,
+	trash *IssueFieldTrashService) (*IssueFieldService, error) {
 
 	if version == "" {
 		return nil, model.ErrNoVersionProvided
@@ -22,6 +23,7 @@ func NewIssueFieldService(client service.Client, version string, configuration *
 		internalClient: &internalIssueFieldServiceImpl{c: client, version: version},
 		Configuration:  configuration,
 		Context:        context,
+		Trash:          trash,
 	}, nil
 }
 
@@ -29,6 +31,7 @@ type IssueFieldService struct {
 	internalClient jira.FieldConnector
 	Configuration  *IssueFieldConfigService
 	Context        *IssueFieldContextService
+	Trash          *IssueFieldTrashService
 }
 
 // Gets returns system and custom issue fields according to the following rules:
@@ -73,7 +76,7 @@ func (i *IssueFieldService) Search(ctx context.Context, options *model.FieldSear
 //
 // DELETE /rest/api/{2-3}/field/{id}
 //
-// TODO: The documentation for the method Field.Delete() needs to be created!
+// https://docs.go-atlassian.io/jira-software-cloud/issues/fields#delete-field
 func (i *IssueFieldService) Delete(ctx context.Context, fieldId string) (*model.TaskScheme, *model.ResponseScheme, error) {
 	return i.internalClient.Delete(ctx, fieldId)
 }
diff --git a/jira/internal/field_trash_impl.go b/jira/internal/field_trash_impl.go
new file mode 100644
index 0000000..0d504b9
--- /dev/null
+++ b/jira/internal/field_trash_impl.go
@@ -0,0 +1,137 @@
+package internal
+
+import (
+	"context"
+	"fmt"
+	model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/service"
+	"github.com/ctreminiom/go-atlassian/service/jira"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+func NewIssueFieldTrashService(client service.Client, version string) (*IssueFieldTrashService, error) {
+
+	if version == "" {
+		return nil, model.ErrNoVersionProvided
+	}
+
+	return &IssueFieldTrashService{
+		internalClient: &internalFieldTrashServiceImpl{c: client, version: version},
+	}, nil
+}
+
+type IssueFieldTrashService struct {
+	internalClient jira.FieldTrashConnector
+}
+
+// Search returns a paginated list of fields in the trash.
+//
+// The list may be restricted to field whose field name or description partially match a string.
+//
+// Only custom fields can be queried, type must be set to custom.
+//
+// GET /rest/api/{2-3}/field/search/trashed
+//
+// https://docs.go-atlassian.io/jira-software-cloud/issues/fields/trash#search-fields-in-trash
+func (i *IssueFieldTrashService) Search(ctx context.Context, options *model.FieldSearchOptionsScheme, startAt, maxResults int) (*model.FieldSearchPageScheme, *model.ResponseScheme, error) {
+	return i.internalClient.Search(ctx, options, startAt, maxResults)
+}
+
+// Move moves a custom field to trash.
+//
+// See Edit or delete a custom field for more information on trashing and deleting custom fields.
+//
+// POST /rest/api/{2-3}/field/{id}/trash
+//
+// https://docs.go-atlassian.io/jira-software-cloud/issues/fields/trash#move-field-to-trash
+func (i *IssueFieldTrashService) Move(ctx context.Context, id string) (*model.ResponseScheme, error) {
+	return i.internalClient.Move(ctx, id)
+}
+
+// Restore restores a custom field from trash.
+//
+// See Edit or delete a custom field for more information on trashing and deleting custom fields.
+//
+// POST /rest/api/{2-3}/field/{id}/restore
+//
+// https://docs.go-atlassian.io/jira-software-cloud/issues/fields/trash#move-field-to-trash
+func (i *IssueFieldTrashService) Restore(ctx context.Context, id string) (*model.ResponseScheme, error) {
+	return i.internalClient.Restore(ctx, id)
+}
+
+type internalFieldTrashServiceImpl struct {
+	c       service.Client
+	version string
+}
+
+func (i *internalFieldTrashServiceImpl) Search(ctx context.Context, options *model.FieldSearchOptionsScheme, startAt, maxResults int) (*model.FieldSearchPageScheme, *model.ResponseScheme, error) {
+
+	params := url.Values{}
+	params.Add("startAt", strconv.Itoa(startAt))
+	params.Add("maxResults", strconv.Itoa(maxResults))
+
+	if options != nil {
+
+		if len(options.IDs) != 0 {
+			params.Add("id", strings.Join(options.IDs, ","))
+		}
+
+		if len(options.OrderBy) != 0 {
+			params.Add("orderBy", options.OrderBy)
+		}
+
+		if len(options.Query) != 0 {
+			params.Add("query", options.Query)
+		}
+	}
+
+	endpoint := fmt.Sprintf("rest/api/%v/field/search/trashed?%v", i.version, params.Encode())
+
+	request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	page := new(model.FieldSearchPageScheme)
+	response, err := i.c.Call(request, page)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return page, response, nil
+}
+
+func (i *internalFieldTrashServiceImpl) Move(ctx context.Context, id string) (*model.ResponseScheme, error) {
+
+	if id == "" {
+		return nil, model.ErrNoFieldIDError
+	}
+
+	endpoint := fmt.Sprintf("rest/api/%v/field/%v/trash", i.version, id)
+
+	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return i.c.Call(request, nil)
+}
+
+func (i *internalFieldTrashServiceImpl) Restore(ctx context.Context, id string) (*model.ResponseScheme, error) {
+
+	if id == "" {
+		return nil, model.ErrNoFieldIDError
+	}
+
+	endpoint := fmt.Sprintf("rest/api/%v/field/%v/restore", i.version, id)
+
+	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return i.c.Call(request, nil)
+}
diff --git a/jira/v2/api_client_impl.go b/jira/v2/api_client_impl.go
index 5299e30..7af3c20 100644
--- a/jira/v2/api_client_impl.go
+++ b/jira/v2/api_client_impl.go
@@ -97,7 +97,12 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
 		return nil, err
 	}
 
-	issueFieldService, err := internal.NewIssueFieldService(client, "2", fieldConfigService, fieldContextService)
+	fieldTrashService, err := internal.NewIssueFieldTrashService(client, "2")
+	if err != nil {
+		return nil, err
+	}
+
+	issueFieldService, err := internal.NewIssueFieldService(client, "2", fieldConfigService, fieldContextService, fieldTrashService)
 	if err != nil {
 		return nil, err
 	}
diff --git a/jira/v3/api_client_impl.go b/jira/v3/api_client_impl.go
index 06da9ff..e96d7d6 100644
--- a/jira/v3/api_client_impl.go
+++ b/jira/v3/api_client_impl.go
@@ -97,7 +97,12 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
 		return nil, err
 	}
 
-	issueFieldService, err := internal.NewIssueFieldService(client, "3", fieldConfigService, fieldContextService)
+	fieldTrashService, err := internal.NewIssueFieldTrashService(client, "3")
+	if err != nil {
+		return nil, err
+	}
+
+	issueFieldService, err := internal.NewIssueFieldService(client, "3", fieldConfigService, fieldContextService, fieldTrashService)
 	if err != nil {
 		return nil, err
 	}
diff --git a/service/jira/attachment.go b/service/jira/attachment.go
index 526a02f..16794cc 100644
--- a/service/jira/attachment.go
+++ b/service/jira/attachment.go
@@ -46,4 +46,13 @@ type AttachmentConnector interface {
 	//
 	// https://docs.go-atlassian.io/jira-software-cloud/issues/attachments#add-attachment
 	Add(ctx context.Context, issueKeyOrId, fileName string, file io.Reader) ([]*model.AttachmentScheme, *model.ResponseScheme, error)
+
+	// Download returns the contents of an attachment. A Range header can be set to define a range of bytes within the attachment to download.
+	//
+	// See the HTTP Range header standard for details.
+	//
+	// GET /rest/api/{2-3}/attachment/content/{id}
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/attachments#download-attachment
+	Download(ctx context.Context, attachmentID string, redirect bool) (*model.ResponseScheme, error)
 }
diff --git a/service/jira/field.go b/service/jira/field.go
index 9922029..b23e096 100644
--- a/service/jira/field.go
+++ b/service/jira/field.go
@@ -42,6 +42,39 @@ type FieldConnector interface {
 	// See Edit or delete a custom field for more information on trashing and deleting custom fields.
 	//
 	// DELETE /rest/api/{2-3}/field/{id}
-	// TODO: The documentation for the method Field.Delete() needs to be created!
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/fields#delete-field
 	Delete(ctx context.Context, fieldId string) (*model.TaskScheme, *model.ResponseScheme, error)
 }
+
+type FieldTrashConnector interface {
+
+	// Search returns a paginated list of fields in the trash.
+	//
+	// The list may be restricted to field whose field name or description partially match a string.
+	//
+	// Only custom fields can be queried, type must be set to custom.
+	//
+	// GET /rest/api/{2-3}/field/search/trashed
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/fields/trash#search-fields-in-trash
+	Search(ctx context.Context, options *model.FieldSearchOptionsScheme, startAt, maxResults int) (*model.FieldSearchPageScheme, *model.ResponseScheme, error)
+
+	// Move moves a custom field to trash.
+	//
+	// See Edit or delete a custom field for more information on trashing and deleting custom fields.
+	//
+	// POST /rest/api/{2-3}/field/{id}/trash
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/fields/trash#move-field-to-trash
+	Move(ctx context.Context, id string) (*model.ResponseScheme, error)
+
+	// Restore restores a custom field from trash.
+	//
+	// See Edit or delete a custom field for more information on trashing and deleting custom fields.
+	//
+	// POST /rest/api/{2-3}/field/{id}/restore
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/fields/trash#move-field-to-trash
+	Restore(ctx context.Context, id string) (*model.ResponseScheme, error)
+}


Test patch:
diff --git a/jira/internal/attachment_impl_test.go b/jira/internal/attachment_impl_test.go
index 18778c1..6311af6 100644
--- a/jira/internal/attachment_impl_test.go
+++ b/jira/internal/attachment_impl_test.go
@@ -770,3 +770,152 @@ func Test_internalIssueAttachmentServiceImpl_Add(t *testing.T) {
 		})
 	}
 }
+
+func Test_internalIssueAttachmentServiceImpl_Download(t *testing.T) {
+
+	type fields struct {
+		c       service.Client
+		version string
+	}
+
+	type args struct {
+		ctx          context.Context
+		attachmentId string
+		redirect     bool
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name:   "when the api version is v2",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:          context.TODO(),
+				attachmentId: "1110",
+				redirect:     false,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"rest/api/2/attachment/content/1110?redirect=false",
+					nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+			},
+		},
+
+		{
+			name:   "when the api version is v3",
+			fields: fields{version: "3"},
+			args: args{
+				ctx:          context.TODO(),
+				attachmentId: "1110",
+				redirect:     true,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"rest/api/3/attachment/content/1110",
+					nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name:   "when the attachment id is not provided",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:          context.TODO(),
+				attachmentId: "",
+			},
+			on: func(fields *fields) {
+				fields.c = mocks.NewClient(t)
+			},
+			wantErr: true,
+			Err:     model.ErrNoAttachmentIDError,
+		},
+
+		{
+			name:   "when the http request cannot be created",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:          context.TODO(),
+				attachmentId: "1110",
+				redirect:     true,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"rest/api/2/attachment/content/1110",
+					nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			attachmentService, err := NewIssueAttachmentService(testCase.fields.c, testCase.fields.version)
+			assert.NoError(t, err)
+
+			gotResponse, err := attachmentService.Download(testCase.args.ctx, testCase.args.attachmentId, testCase.args.redirect)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+			}
+
+		})
+	}
+}
diff --git a/jira/internal/field_impl_test.go b/jira/internal/field_impl_test.go
index ab63b28..6d8903e 100644
--- a/jira/internal/field_impl_test.go
+++ b/jira/internal/field_impl_test.go
@@ -122,7 +122,7 @@ func Test_internalIssueFieldServiceImpl_Gets(t *testing.T) {
 				testCase.on(&testCase.fields)
 			}
 
-			fieldService, err := NewIssueFieldService(testCase.fields.c, testCase.fields.version, nil, nil)
+			fieldService, err := NewIssueFieldService(testCase.fields.c, testCase.fields.version, nil, nil, nil)
 			assert.NoError(t, err)
 
 			gotResult, gotResponse, err := fieldService.Gets(testCase.args.ctx)
@@ -322,7 +322,7 @@ func Test_internalIssueFieldServiceImpl_Create(t *testing.T) {
 				testCase.on(&testCase.fields)
 			}
 
-			fieldService, err := NewIssueFieldService(testCase.fields.c, testCase.fields.version, nil, nil)
+			fieldService, err := NewIssueFieldService(testCase.fields.c, testCase.fields.version, nil, nil, nil)
 			assert.NoError(t, err)
 
 			gotResult, gotResponse, err := fieldService.Create(testCase.args.ctx, testCase.args.payload)
@@ -508,7 +508,7 @@ func Test_internalIssueFieldServiceImpl_Search(t *testing.T) {
 				testCase.on(&testCase.fields)
 			}
 
-			fieldService, err := NewIssueFieldService(testCase.fields.c, testCase.fields.version, nil, nil)
+			fieldService, err := NewIssueFieldService(testCase.fields.c, testCase.fields.version, nil, nil, nil)
 			assert.NoError(t, err)
 
 			gotResult, gotResponse, err := fieldService.Search(testCase.args.ctx, testCase.args.options,
@@ -657,7 +657,7 @@ func Test_internalIssueFieldServiceImpl_Delete(t *testing.T) {
 				testCase.on(&testCase.fields)
 			}
 
-			fieldService, err := NewIssueFieldService(testCase.fields.c, testCase.fields.version, nil, nil)
+			fieldService, err := NewIssueFieldService(testCase.fields.c, testCase.fields.version, nil, nil, nil)
 			assert.NoError(t, err)
 
 			gotResult, gotResponse, err := fieldService.Delete(testCase.args.ctx, testCase.args.fieldId)
diff --git a/jira/internal/field_trash_impl_test.go b/jira/internal/field_trash_impl_test.go
new file mode 100644
index 0000000..5f65481
--- /dev/null
+++ b/jira/internal/field_trash_impl_test.go
@@ -0,0 +1,487 @@
+package internal
+
+import (
+	"context"
+	"errors"
+	model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/service"
+	"github.com/ctreminiom/go-atlassian/service/mocks"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"testing"
+)
+
+func Test_internalFieldTrashServiceImpl_Search(t *testing.T) {
+
+	type fields struct {
+		c       service.Client
+		version string
+	}
+
+	type args struct {
+		ctx                 context.Context
+		options             *model.FieldSearchOptionsScheme
+		startAt, maxResults int
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name:   "when the api version is v3",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+				options: &model.FieldSearchOptionsScheme{
+					IDs:     []string{"111", "12222"},
+					Query:   "query-sample",
+					OrderBy: "lastUsed",
+				},
+				startAt:    0,
+				maxResults: 50,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"rest/api/3/field/search/trashed?id=111%2C12222&maxResults=50&orderBy=lastUsed&query=query-sample&startAt=0",
+					nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.FieldSearchPageScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+			wantErr: false,
+			Err:     nil,
+		},
+
+		{
+			name:   "when the api version is v2",
+			fields: fields{version: "2"},
+			args: args{
+				ctx: context.TODO(),
+				options: &model.FieldSearchOptionsScheme{
+					IDs:     []string{"111", "12222"},
+					Query:   "query-sample",
+					OrderBy: "lastUsed",
+				},
+				startAt:    0,
+				maxResults: 50,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"rest/api/2/field/search/trashed?id=111%2C12222&maxResults=50&orderBy=lastUsed&query=query-sample&startAt=0",
+					nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.FieldSearchPageScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+			wantErr: false,
+			Err:     nil,
+		},
+
+		{
+			name:   "when the http request cannot be created",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+				options: &model.FieldSearchOptionsScheme{
+					IDs:     []string{"111", "12222"},
+					Query:   "query-sample",
+					OrderBy: "lastUsed",
+				},
+				startAt:    0,
+				maxResults: 50,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"rest/api/3/field/search/trashed?id=111%2C12222&maxResults=50&orderBy=lastUsed&query=query-sample&startAt=0",
+					nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name:   "when the http request cannot be created",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"rest/api/3/field/search/trashed?maxResults=0&startAt=0",
+					nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			fieldService, err := NewIssueFieldTrashService(testCase.fields.c, testCase.fields.version)
+			assert.NoError(t, err)
+
+			gotResult, gotResponse, err := fieldService.Search(testCase.args.ctx, testCase.args.options,
+				testCase.args.startAt, testCase.args.maxResults)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+
+		})
+	}
+}
+
+func Test_internalFieldTrashServiceImpl_Move(t *testing.T) {
+
+	type fields struct {
+		c       service.Client
+		version string
+	}
+
+	type args struct {
+		ctx context.Context
+		id  string
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name:   "when the api version is v3",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+				id:  "customfield_12000",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"rest/api/3/field/customfield_12000/trash",
+					nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+			wantErr: false,
+			Err:     nil,
+		},
+
+		{
+			name:   "when the api version is v2",
+			fields: fields{version: "2"},
+			args: args{
+				ctx: context.TODO(),
+				id:  "customfield_12000",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"rest/api/2/field/customfield_12000/trash",
+					nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+			wantErr: false,
+			Err:     nil,
+		},
+
+		{
+			name:   "when the field id is not provided",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+				id:  "",
+			},
+			wantErr: true,
+			Err:     model.ErrNoFieldIDError,
+		},
+
+		{
+			name:   "when the http request cannot be created",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+				id:  "customfield_12000",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"rest/api/3/field/customfield_12000/trash",
+					nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			fieldService, err := NewIssueFieldTrashService(testCase.fields.c, testCase.fields.version)
+			assert.NoError(t, err)
+
+			gotResponse, err := fieldService.Move(testCase.args.ctx, testCase.args.id)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+			}
+
+		})
+	}
+}
+
+func Test_internalFieldTrashServiceImpl_Restore(t *testing.T) {
+
+	type fields struct {
+		c       service.Client
+		version string
+	}
+
+	type args struct {
+		ctx context.Context
+		id  string
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name:   "when the api version is v3",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+				id:  "customfield_12000",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"rest/api/3/field/customfield_12000/restore",
+					nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+			wantErr: false,
+			Err:     nil,
+		},
+
+		{
+			name:   "when the api version is v2",
+			fields: fields{version: "2"},
+			args: args{
+				ctx: context.TODO(),
+				id:  "customfield_12000",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"rest/api/2/field/customfield_12000/restore",
+					nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+			wantErr: false,
+			Err:     nil,
+		},
+
+		{
+			name:   "when the field id is not provided",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+				id:  "",
+			},
+			wantErr: true,
+			Err:     model.ErrNoFieldIDError,
+		},
+
+		{
+			name:   "when the http request cannot be created",
+			fields: fields{version: "3"},
+			args: args{
+				ctx: context.TODO(),
+				id:  "customfield_12000",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"rest/api/3/field/customfield_12000/restore",
+					nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			fieldService, err := NewIssueFieldTrashService(testCase.fields.c, testCase.fields.version)
+			assert.NoError(t, err)
+
+			gotResponse, err := fieldService.Restore(testCase.args.ctx, testCase.args.id)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+			}
+
+		})
+	}
+}