Repository: ctreminiom/go-atlassian
Pull request #110 (2022-03-08 04:48:42)

Feature/long task archive 


Patch:
diff --git a/confluence/confluence.go b/confluence/confluence.go
index 22bf045..262a82e 100644
--- a/confluence/confluence.go
+++ b/confluence/confluence.go
@@ -18,11 +18,12 @@ type Client struct {
 	HTTP *http.Client
 	Site *url.URL
 
-	Auth    *AuthenticationService
-	Content *ContentService
-	Space   *SpaceService
-	Label   *LabelService
-	Search  *SearchService
+	Auth     *AuthenticationService
+	Content  *ContentService
+	Space    *SpaceService
+	Label    *LabelService
+	Search   *SearchService
+	LongTask *LongTaskService
 }
 
 func New(httpClient *http.Client, site string) (client *Client, err error) {
@@ -70,6 +71,7 @@ func New(httpClient *http.Client, site string) (client *Client, err error) {
 
 	client.Label = &LabelService{client: client}
 	client.Search = &SearchService{client: client}
+	client.LongTask = &LongTaskService{client: client}
 	return
 }
 
diff --git a/confluence/content.go b/confluence/content.go
index 763740e..a38d467 100644
--- a/confluence/content.go
+++ b/confluence/content.go
@@ -304,3 +304,31 @@ func (c *ContentService) History(ctx context.Context, contentID string, expand [
 
 	return
 }
+
+// Archive archives a list of pages. The pages to be archived are specified as a list of content IDs.
+// This API accepts the archival request and returns a task ID. The archival process happens asynchronously.
+// Use the /longtask/ REST API to get the copy task status.
+// Docs: https://docs.go-atlassian.io/confluence-cloud/content#archive-pages
+func (c *ContentService) Archive(ctx context.Context, payload *model.ContentArchivePayloadScheme) (result *model.ContentArchiveResultScheme, response *ResponseScheme, err error) {
+
+	payloadAsReader, err := transformStructToReader(payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	endpoint := "/wiki/rest/api/content/archive"
+
+	request, err := c.client.newRequest(ctx, http.MethodPost, endpoint, payloadAsReader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	request.Header.Add("Content-Type", "application/json")
+
+	response, err = c.client.Call(request, &result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return
+}
diff --git a/confluence/longTask.go b/confluence/longTask.go
new file mode 100644
index 0000000..e95facf
--- /dev/null
+++ b/confluence/longTask.go
@@ -0,0 +1,62 @@
+package confluence
+
+import (
+	"context"
+	"fmt"
+	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"net/http"
+	"net/url"
+	"strconv"
+)
+
+type LongTaskService struct{ client *Client }
+
+// Gets returns information about all active long-running tasks (e.g. space export),
+// such as how long each task has been running and the percentage of each task that has completed.
+// Docs: https://docs.go-atlassian.io/confluence-cloud/long-task#get-long-running-tasks
+func (l *LongTaskService) Gets(ctx context.Context, start, limit int) (result *models.LongTaskPageScheme,
+	response *ResponseScheme, err error) {
+
+	query := url.Values{}
+	query.Add("start", strconv.Itoa(start))
+	query.Add("limit", strconv.Itoa(limit))
+
+	var endpoint = fmt.Sprintf("/wiki/rest/api/longtask?%v", query.Encode())
+
+	request, err := l.client.newRequest(ctx, http.MethodGet, endpoint, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	request.Header.Set("Accept", "application/json")
+
+	response, err = l.client.Call(request, &result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return
+}
+
+// Get returns information about an active long-running task (e.g. space export), such as how long it has been running
+//and the percentage of the task that has completed.
+// Docs: https://docs.go-atlassian.io/confluence-cloud/long-task#get-long-running-task
+func (l *LongTaskService) Get(ctx context.Context, taskID string) (result *models.LongTaskScheme, response *ResponseScheme,
+	err error) {
+
+	var endpoint = fmt.Sprintf("/wiki/rest/api/longtask/%v", taskID)
+
+	request, err := l.client.newRequest(ctx, http.MethodGet, endpoint, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	request.Header.Set("Accept", "application/json")
+
+	response, err = l.client.Call(request, &result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return
+}
diff --git a/confluence/mocks/archive-content.json b/confluence/mocks/archive-content.json
new file mode 100644
index 0000000..700ad14
--- /dev/null
+++ b/confluence/mocks/archive-content.json
@@ -0,0 +1,6 @@
+{
+  "id": "195657768",
+  "links": {
+    "status": "/rest/api/longtask/195657768"
+  }
+}
\ No newline at end of file
diff --git a/confluence/mocks/get-long-tasks.json b/confluence/mocks/get-long-tasks.json
new file mode 100644
index 0000000..e1b4496
--- /dev/null
+++ b/confluence/mocks/get-long-tasks.json
@@ -0,0 +1,44 @@
+{
+  "results": [
+    {
+      "id": "<string>",
+      "name": {
+        "key": "<string>",
+        "args": [
+          {}
+        ]
+      },
+      "elapsedTime": 2154,
+      "percentageComplete": 2154,
+      "successful": true,
+      "finished": true,
+      "messages": [
+        {
+          "translation": "<string>",
+          "args": [
+            "<string>"
+          ]
+        }
+      ],
+      "status": "<string>",
+      "errors": [
+        {
+          "translation": "<string>",
+          "args": [
+            "<string>"
+          ]
+        }
+      ],
+      "additionalDetails": {
+        "destinationId": "<string>",
+        "destinationUrl": "<string>",
+        "totalPageNeedToCopy": 2154,
+        "additionalProperties": "<string>"
+      }
+    }
+  ],
+  "start": 2154,
+  "limit": 2154,
+  "size": 2154,
+  "_links": {}
+}
\ No newline at end of file
diff --git a/pkg/infra/models/confluence_content.go b/pkg/infra/models/confluence_content.go
index 1942ee4..1f52d8a 100644
--- a/pkg/infra/models/confluence_content.go
+++ b/pkg/infra/models/confluence_content.go
@@ -195,3 +195,18 @@ type UserPersonalDetailScheme struct {
 	Website string `json:"website,omitempty"`
 	Email   string `json:"email,omitempty"`
 }
+
+type ContentArchivePayloadScheme struct {
+	Pages []*ContentArchiveIDPayloadScheme `json:"pages,omitempty"`
+}
+
+type ContentArchiveIDPayloadScheme struct {
+	ID int `json:"id,omitempty"`
+}
+
+type ContentArchiveResultScheme struct {
+	ID    string `json:"id"`
+	Links struct {
+		Status string `json:"status"`
+	} `json:"links"`
+}
diff --git a/pkg/infra/models/confluence_long_task.go b/pkg/infra/models/confluence_long_task.go
new file mode 100644
index 0000000..5fdf5cf
--- /dev/null
+++ b/pkg/infra/models/confluence_long_task.go
@@ -0,0 +1,37 @@
+package models
+
+type LongTaskPageScheme struct {
+	Results []*LongTaskScheme `json:"results,omitempty"`
+	Start   int               `json:"start,omitempty"`
+	Limit   int               `json:"limit,omitempty"`
+	Size    int               `json:"size,omitempty"`
+}
+
+type LongTaskScheme struct {
+	ID                 string                   `json:"id,omitempty"`
+	Name               *LongTaskNameScheme      `json:"name,omitempty"`
+	ElapsedTime        int                      `json:"elapsedTime,omitempty"`
+	PercentageComplete int                      `json:"percentageComplete,omitempty"`
+	Successful         bool                     `json:"successful,omitempty"`
+	Finished           bool                     `json:"finished,omitempty"`
+	Messages           []*LongTaskMessageScheme `json:"messages,omitempty"`
+	Status             string                   `json:"status,omitempty"`
+	Errors             []*LongTaskMessageScheme `json:"errors,omitempty"`
+	AdditionalDetails  *LongTaskDetailsScheme   `json:"additionalDetails,omitempty"`
+}
+
+type LongTaskNameScheme struct {
+	Key string `json:"key,omitempty"`
+}
+
+type LongTaskMessageScheme struct {
+	Translation string   `json:"translation,omitempty"`
+	Args        []string `json:"args,omitempty"`
+}
+
+type LongTaskDetailsScheme struct {
+	DestinationID        string `json:"destinationId,omitempty"`
+	DestinationURL       string `json:"destinationUrl,omitempty"`
+	TotalPageNeedToCopy  int    `json:"totalPageNeedToCopy,omitempty"`
+	AdditionalProperties string `json:"additionalProperties,omitempty"`
+}


Test patch:
diff --git a/confluence/content_test.go b/confluence/content_test.go
index 4b227f2..17e3064 100644
--- a/confluence/content_test.go
+++ b/confluence/content_test.go
@@ -11,6 +11,186 @@ import (
 	"time"
 )
 
+func TestContentService_Archive(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		payload            *model.ContentArchivePayloadScheme
+		mockFile           string
+		wantHTTPMethod     string
+		endpoint           string
+		context            context.Context
+		wantHTTPCodeReturn int
+		wantErr            bool
+		expectedError      string
+	}{
+		{
+			name: "when the parameters are correct",
+			payload: &model.ContentArchivePayloadScheme{
+				Pages: []*model.ContentArchiveIDPayloadScheme{
+					{
+						ID: 1001,
+					},
+					{
+						ID: 1001,
+					},
+					{
+						ID: 1001,
+					},
+				}},
+			wantHTTPMethod:     http.MethodPost,
+			mockFile:           "./mocks/archive-content.json",
+			endpoint:           "/wiki/rest/api/content/archive",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            false,
+		},
+
+		{
+			name: "when the response body is empty",
+			payload: &model.ContentArchivePayloadScheme{
+				Pages: []*model.ContentArchiveIDPayloadScheme{
+					{
+						ID: 1001,
+					},
+					{
+						ID: 1001,
+					},
+					{
+						ID: 1001,
+					},
+				}},
+			wantHTTPMethod:     http.MethodPost,
+			mockFile:           "./mocks/empty-json.json",
+			endpoint:           "/wiki/rest/api/content/archive",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "unexpected end of JSON input",
+		},
+
+		{
+			name:               "when the payload is not provided",
+			payload:            nil,
+			wantHTTPMethod:     http.MethodPost,
+			mockFile:           "./mocks/archive-content.json",
+			endpoint:           "/wiki/rest/api/content/archive",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "failed to parse the interface pointer, please provide a valid one",
+		},
+
+		{
+			name: "when the response status is not valid",
+			payload: &model.ContentArchivePayloadScheme{
+				Pages: []*model.ContentArchiveIDPayloadScheme{
+					{
+						ID: 1001,
+					},
+					{
+						ID: 1001,
+					},
+					{
+						ID: 1001,
+					},
+				}},
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/wiki/rest/api/content/archive",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusBadRequest,
+			wantErr:            true,
+			expectedError:      "unexpected end of JSON input",
+		},
+
+		{
+			name: "when the context is not provided",
+			payload: &model.ContentArchivePayloadScheme{
+				Pages: []*model.ContentArchiveIDPayloadScheme{
+					{
+						ID: 1001,
+					},
+					{
+						ID: 1001,
+					},
+					{
+						ID: 1001,
+					},
+				}},
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/wiki/rest/api/content/archive",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "request creation failed: net/http: nil Context",
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			//Init a new HTTP mock server
+			mockOptions := mockServerOptions{
+				Endpoint:           testCase.endpoint,
+				MockFilePath:       testCase.mockFile,
+				MethodAccepted:     testCase.wantHTTPMethod,
+				ResponseCodeWanted: testCase.wantHTTPCodeReturn,
+			}
+
+			mockServer, err := startMockServer(&mockOptions)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			defer mockServer.Close()
+
+			//Init the library instance
+			mockClient, err := startMockClient(mockServer.URL)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			implementation := &ContentService{client: mockClient}
+
+			gotResult, gotResponse, err := implementation.Archive(testCase.context, testCase.payload)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.expectedError)
+
+				if gotResponse != nil {
+					t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				}
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+
+				apiEndpoint, err := url.Parse(gotResponse.Endpoint)
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				var endpointToAssert string
+
+				if apiEndpoint.Query().Encode() != "" {
+					endpointToAssert = fmt.Sprintf("%v?%v", apiEndpoint.Path, apiEndpoint.Query().Encode())
+				} else {
+					endpointToAssert = apiEndpoint.Path
+				}
+
+				t.Logf("HTTP Endpoint Wanted: %v, HTTP Endpoint Returned: %v", testCase.endpoint, endpointToAssert)
+				assert.Equal(t, testCase.endpoint, endpointToAssert)
+			}
+		})
+	}
+}
+
 func TestContentService_Create(t *testing.T) {
 
 	testCases := []struct {
diff --git a/confluence/longTask_test.go b/confluence/longTask_test.go
new file mode 100644
index 0000000..d53df63
--- /dev/null
+++ b/confluence/longTask_test.go
@@ -0,0 +1,242 @@
+package confluence
+
+import (
+	"context"
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"net/url"
+	"testing"
+)
+
+func TestLongTaskService_Gets(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		start              int
+		limit              int
+		mockFile           string
+		wantHTTPMethod     string
+		endpoint           string
+		context            context.Context
+		wantHTTPCodeReturn int
+		wantErr            bool
+		expectedError      string
+	}{
+		{
+			name:               "when the parameters are correct",
+			start:              0,
+			limit:              50,
+			mockFile:           "./mocks/get-long-tasks.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/longtask?limit=50&start=0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            false,
+		},
+
+		{
+			name:               "when the response body is empty",
+			start:              0,
+			limit:              50,
+			mockFile:           "./mocks/empty-json.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/longtask?limit=50&start=0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "unexpected end of JSON input",
+		},
+
+		{
+			name:               "when the context is not provided",
+			start:              0,
+			limit:              50,
+			mockFile:           "./mocks/get-long-tasks.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/longtask?limit=50&start=0",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "request creation failed: net/http: nil Context",
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			//Init a new HTTP mock server
+			mockOptions := mockServerOptions{
+				Endpoint:           testCase.endpoint,
+				MockFilePath:       testCase.mockFile,
+				MethodAccepted:     testCase.wantHTTPMethod,
+				ResponseCodeWanted: testCase.wantHTTPCodeReturn,
+			}
+
+			mockServer, err := startMockServer(&mockOptions)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			defer mockServer.Close()
+
+			//Init the library instance
+			mockClient, err := startMockClient(mockServer.URL)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			implementation := &LongTaskService{client: mockClient}
+
+			gotResult, gotResponse, err := implementation.Gets(testCase.context, testCase.start, testCase.limit)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.expectedError)
+
+				if gotResponse != nil {
+					t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				}
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+
+				apiEndpoint, err := url.Parse(gotResponse.Endpoint)
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				var endpointToAssert string
+
+				if apiEndpoint.Query().Encode() != "" {
+					endpointToAssert = fmt.Sprintf("%v?%v", apiEndpoint.Path, apiEndpoint.Query().Encode())
+				} else {
+					endpointToAssert = apiEndpoint.Path
+				}
+
+				t.Logf("HTTP Endpoint Wanted: %v, HTTP Endpoint Returned: %v", testCase.endpoint, endpointToAssert)
+				assert.Equal(t, testCase.endpoint, endpointToAssert)
+			}
+		})
+	}
+}
+
+func TestLongTaskService_Get(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		taskID             string
+		mockFile           string
+		wantHTTPMethod     string
+		endpoint           string
+		context            context.Context
+		wantHTTPCodeReturn int
+		wantErr            bool
+		expectedError      string
+	}{
+		{
+			name:               "when the parameters are correct",
+			taskID:             "1100001",
+			mockFile:           "./mocks/get-long-task.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/longtask/1100001",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            false,
+		},
+
+		{
+			name:               "when the response body is empty",
+			taskID:             "1100001",
+			mockFile:           "./mocks/empty-json.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/longtask/1100001",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "unexpected end of JSON input",
+		},
+
+		{
+			name:               "when the context is not provided",
+			taskID:             "1100001",
+			mockFile:           "./mocks/get-long-task.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/longtask/1100001",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "request creation failed: net/http: nil Context",
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			//Init a new HTTP mock server
+			mockOptions := mockServerOptions{
+				Endpoint:           testCase.endpoint,
+				MockFilePath:       testCase.mockFile,
+				MethodAccepted:     testCase.wantHTTPMethod,
+				ResponseCodeWanted: testCase.wantHTTPCodeReturn,
+			}
+
+			mockServer, err := startMockServer(&mockOptions)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			defer mockServer.Close()
+
+			//Init the library instance
+			mockClient, err := startMockClient(mockServer.URL)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			implementation := &LongTaskService{client: mockClient}
+
+			gotResult, gotResponse, err := implementation.Get(testCase.context, testCase.taskID)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.expectedError)
+
+				if gotResponse != nil {
+					t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				}
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+
+				apiEndpoint, err := url.Parse(gotResponse.Endpoint)
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				var endpointToAssert string
+
+				if apiEndpoint.Query().Encode() != "" {
+					endpointToAssert = fmt.Sprintf("%v?%v", apiEndpoint.Path, apiEndpoint.Query().Encode())
+				} else {
+					endpointToAssert = apiEndpoint.Path
+				}
+
+				t.Logf("HTTP Endpoint Wanted: %v, HTTP Endpoint Returned: %v", testCase.endpoint, endpointToAssert)
+				assert.Equal(t, testCase.endpoint, endpointToAssert)
+			}
+		})
+	}
+}