Repository: ctreminiom/go-atlassian
Pull request #101 (2022-01-23 21:50:34)

Feature/confluence services


Patch:
diff --git a/README.md b/README.md
index a025a5a..abf0e41 100644
--- a/README.md
+++ b/README.md
@@ -13,107 +13,160 @@
     <a href="https://bestpractices.coreinfrastructure.org/projects/4861"><img src="https://bestpractices.coreinfrastructure.org/projects/4861/badge"></a> 
 </p>
 
-go-atlassian is a Go module that enables the interaction with the Atlassian Cloud Services. It provides the Go implementation for operating the Atlassian Cloud platform.
-
-## ✨ Features
-- Supports Jira Software V2/V3 endpoints.
-- Support the Jira Agile endpoints
-- Interacts with the Jira Service Management entities.
-- Manages the Atlassian Admin Cloud organizations.
-- Manages the Atlassian Admin SCIM workflow.
-- Checks Confluence Cloud content permissions.
-- CRUD Confluence Cloud content (page, blogpost, comment, question).
-- Add attachment into Confluence Cloud contents.
-- Search contents and spaces.
-- Support the Atlassian Document Format (ADF).  
-- Every method has their corresponding example documented.
-
-##  🔰 Installation
-Make sure you have Go installed (download). Version `1.13` or higher is required.
-```sh
-$ go get -u -v github.com/ctreminiom/go-atlassian
+Communicate with the [Atlassian API's](https://developer.atlassian.com/cloud/) quickly and easily
+with the **go-atlassian** module. With the **go-atlassian** client, you can retrieve and manipulate
+the extensive Atlassian Cloud API's like Jira, Confluence, Jira Agile, Jira Service Management, Atlassian Admin and much more!.
+
+If you find an endpoint not supported, please submit a pull request or raise a feature issue - it's always greatly appreciated.
+
+## Installation
+
+If you do not have [Go](https://golang.org/) installed yet, you can find installation instructions
+[here](https://golang.org/doc/install). Please note that the package requires Go version
+1.13 or later for module support.
+
+To pull the most recent version of **go-atlassian**, use `go get`.
+
+```
+go get github.com/ctreminiom/go-atlassian
 ```
 
-## 📓 Documentation
-Documentation is hosted live at https://docs.go-atlassian.io/
+Then import the package into your project as you normally would. You can import the following packages
 
-## 📝 Using the library
-````go
-package main
+|Package|import path  |
+|--|--|
+|Jira v2|`github.com/ctreminiom/go-atlassian/jira/v2`|
+|Jira v3|`github.com/ctreminiom/go-atlassian/jira/v3`|
+|Jira Agile|`github.com/ctreminiom/go-atlassian/jira/agile`|
+|Jira ITSM|`github.com/ctreminiom/go-atlassian/jira/sm`|
+|Confluence|`github.com/ctreminiom/go-atlassian/confluence`|
+|Cloud Admin|`github.com/ctreminiom/go-atlassian/admin`|
 
-import (
-	"context"
-	"github.com/ctreminiom/go-atlassian/jira/v2"
-	"log"
-	"os"
-)
+Now you're ready to Go.
+
+### 🧳 Creating A Client
+
+Before using the **go-atlassian** package, you need to have an Atlassian API key. If you do
+not have a key yet, you can sign up [here](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/).
+
+Create a client with your instance host and access token to start communicating with the Atlassian API's.
+
+```go
+instance, err := confluence.New(nil, "INSTANCE_HOST")
+if err != nil {
+    log.Fatal(err)
+}
+
+instance.Auth.SetBasicAuth("YOUR_CLIENT_MAIL", "YOUR_APP_ACCESS_TOKEN")
+```
+
+If you need to use a preconfigured HTTP client, simply pass its address to the
+`New` function.
+
+```go  
+transport := http.Transport{
+	Proxy: http.ProxyFromEnvironment,
+	Dial: (&net.Dialer{
+		// Modify the time to wait for a connection to establish
+		Timeout:   1 * time.Second,
+		KeepAlive: 30 * time.Second,
+	}).Dial,
+	TLSHandshakeTimeout: 10 * time.Second,
+}
+
+client := http.Client{
+	Transport: &transport,
+	Timeout:   4 * time.Second,
+}
 
-func main() {
+instance, err := confluence.New(&client, "INSTANCE_HOST")
+if err != nil {
+	log.Fatal(err)
+}
 
-	var (
-		host  = os.Getenv("HOST")
-		mail  = os.Getenv("MAIL")
-		token = os.Getenv("TOKEN")
-	)
+instance.Auth.SetBasicAuth("YOUR_CLIENT_MAIL", "YOUR_APP_ACCESS_TOKEN")
+```
 
-	atlassian, err := v2.New(nil, host)
-	if err != nil {
-		return
-	}
+### 🗺️ Services
 
-	atlassian.Auth.SetBasicAuth(mail, token)
+The client contains a distinct service for working with each of the Atlassian API's
+endpoints. Each service has a set of service functions that make specific API
+calls to their respective endpoint.
 
-	issue, response, err := atlassian.Issue.Get(context.Background(), "KP-2", nil, []string{"transitions"})
-	if err != nil {
-		log.Fatal(err)
-	}
+To start communicating with the **go-atlassian**, choose a service and call its service
+function. Take the Jira service for example.
 
-	log.Println("HTTP Endpoint Used", response.Endpoint)
+To get the issue with the transitions, use the **Issue** service function.
+```go  
+ctx := context.Background()
+issueKey := "KP-2"
+expand := []string{"transitions"}
 
-	log.Println(issue.Key)
-	log.Println(issue.Fields.Reporter.AccountID)
+issue, response, err := atlassian.Issue.Get(ctx,issueKey, nil, expand)
+if err != nil {
+	log.Fatal(err)
+}
 
-	for _, transition := range issue.Transitions {
-		log.Println(transition.Name, transition.ID, transition.To.ID, transition.HasScreen)
-	}
+log.Println(issue.Key)
 
-	// Check if the issue contains sub-tasks
-	if issue.Fields.Subtasks != nil {
-		for _, subTask := range issue.Fields.Subtasks {
-			log.Println("Sub-Task: ", subTask.Key, subTask.Fields.Summary)
-		}
-	}
+for _, transition := range issue.Transitions {
+	log.Println(transition.Name, transition.ID, transition.To.ID, transition.HasScreen)
 }
-````
+```
 
-## ⭐️ Project assistance
-If you want to say **thank you** or/and support active development of `go-atlassian`:
+To search issues using a JQL query, use the **Issue.Search** service function.
+```go  
+var (
+	jql    = "order by created DESC"
+	fields = []string{"status"}
+	expand = []string{"changelog", "renderedFields", "names", "schema", "transitions", "operations", "editmeta"}
+)
+
+issues, response, err := atlassian.Issue.Search.Post(context.Background(), jql, fields, expand, 0, 50, "")
+if err != nil {
+	log.Fatal(err)
+}
+
+log.Println("HTTP Endpoint Used", response.Endpoint)
+log.Println(issues.Total)
+```
+
+The rest of the service functions work much the same way; they are concise and
+behave as you would expect. The [documentation](https://docs.go-atlassian.io/)
+contains several examples on how to use each service function.
+
+## ✍️ Contributions
+
+If you would like to contribute to this project, please adhere to the following
+guidelines.
+
+* Submit an issue describing the problem.
+* Fork the repo and add your contribution.
+* Add appropriate tests.
+* Run go fmt, go vet, and golint.
+* Prefer idiomatic Go over non-idiomatic code.
+* Follow the basic Go conventions found [here](https://github.com/golang/go/wiki/CodeReviewComments).
+* If in doubt, try to match your code to the current codebase.
+* Create a pull request with a description of your changes.
+
+Again, contributions are greatly appreciated!
 
-- Add a [GitHub Star](https://github.com/ctreminiom/go-atlassian) to the project.
-- Write interesting articles about project on [Dev.to](https://dev.to/), [Medium](https://medium.com/) or personal blog.
-- Contributions, issues and feature requests are welcome!
--  Feel free to check [issues page](https://github.com/ctreminiom/go-atlassian/issues).
 
 ## 💡 Inspiration
 The project was created with the purpose to provide a unique point to provide an interface for interacting with Atlassian products. This module is highly inspired by the Go library https://github.com/andygrunwald/go-jira
 but focused on Cloud solutions.
 
-## 🧪 Run Test Cases
-```sh
-go test -v ./...
-```
-
 ## 📝 License
 Copyright © 2021 [Carlos Treminio](https://github.com/ctreminiom).
 This project is [MIT](https://opensource.org/licenses/MIT) licensed.
 
 [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fctreminiom%2Fgo-atlassian.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fctreminiom%2Fgo-atlassian?ref=badge_large)
 
-## 🌐 Credits
-In addition to all the contributors we would like to thank to these companies:
+## 🤝 Special Thanks
+In addition to all the contributors we would like to thanks to these companies:
 -   [Atlassian](https://www.atlassian.com/)  for providing us Atlassian Admin/Jira/Confluence Standard licenses.
 -   [JetBrains](https://www.jetbrains.com/)  for providing us with free licenses of  [GoLand](https://www.jetbrains.com/pycharm/)
 -   [GitBook](https://www.gitbook.com/)  for providing us non-profit / open-source plan so hence I would like to express my thanks here.
 
-<img src="./static/jetbrains-logo.svg">
-<img src="./static/gitbook-logo.svg">
\ No newline at end of file
+<img align="right" src="./static/jetbrains-logo.svg">
+<img align="left" src="./static/gitbook-logo.svg">
\ No newline at end of file
diff --git a/confluence/confluence.go b/confluence/confluence.go
index 125300e..dcd3a3c 100644
--- a/confluence/confluence.go
+++ b/confluence/confluence.go
@@ -21,6 +21,7 @@ type Client struct {
 	Auth    *AuthenticationService
 	Content *ContentService
 	Space   *SpaceService
+	Label   *LabelService
 }
 
 func New(httpClient *http.Client, site string) (client *Client, err error) {
@@ -58,9 +59,11 @@ func New(httpClient *http.Client, site string) (client *Client, err error) {
 				Group:  &ContentRestrictionOperationGroupService{client: client},
 				User:   &ContentRestrictionOperationUserService{client: client},
 			}},
+		Version: &ContentVersionService{client: client},
 	}
 
 	client.Space = &SpaceService{client: client}
+	client.Label = &LabelService{client: client}
 	return
 }
 
diff --git a/confluence/content.go b/confluence/content.go
index 9bd0402..706ac47 100644
--- a/confluence/content.go
+++ b/confluence/content.go
@@ -19,6 +19,7 @@ type ContentService struct {
 	Label              *ContentLabelService
 	Property           *ContentPropertyService
 	Restriction        *ContentRestrictionService
+	Version            *ContentVersionService
 }
 
 // Gets returns all content in a Confluence instance.
diff --git a/confluence/contentVersion.go b/confluence/contentVersion.go
new file mode 100644
index 0000000..aa6d29f
--- /dev/null
+++ b/confluence/contentVersion.go
@@ -0,0 +1,151 @@
+package confluence
+
+import (
+	"context"
+	"fmt"
+	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+type ContentVersionService struct{ client *Client }
+
+// Gets returns the versions for a piece of content in descending order.
+// Docs: https://docs.go-atlassian.io/confluence-cloud/content/versions#get-content-versions
+func (c *ContentVersionService) Gets(ctx context.Context, contentID string, expand []string, start, limit int) (
+	result *models.ContentVersionPageScheme, response *ResponseScheme, err error) {
+
+	if len(contentID) == 0 {
+		return nil, nil, models.ErrNoContentIDError
+	}
+
+	query := url.Values{}
+	query.Add("start", strconv.Itoa(start))
+	query.Add("limit", strconv.Itoa(limit))
+
+	if len(expand) != 0 {
+		query.Add("expand", strings.Join(expand, ","))
+	}
+
+	endpoint := fmt.Sprintf("wiki/rest/api/content/%v/version?%v", contentID, query.Encode())
+
+	request, err := c.client.newRequest(ctx, http.MethodGet, endpoint, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	request.Header.Set("Accept", "application/json")
+
+	response, err = c.client.Call(request, &result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return
+}
+
+// Get returns a version for a piece of content.
+// Docs: https://docs.go-atlassian.io/confluence-cloud/content/versions#get-content-version
+func (c *ContentVersionService) Get(ctx context.Context, contentID string, versionNumber int, expand []string) (
+	result *models.ContentVersionScheme, response *ResponseScheme, err error) {
+
+	if len(contentID) == 0 {
+		return nil, nil, models.ErrNoContentIDError
+	}
+
+	var endpoint strings.Builder
+	endpoint.WriteString(fmt.Sprintf("wiki/rest/api/content/%v/version/%v", contentID, versionNumber))
+
+	if len(expand) != 0 {
+		query := url.Values{}
+		query.Add("expand", strings.Join(expand, ","))
+		endpoint.WriteString(fmt.Sprintf("?%v", query.Encode()))
+	}
+
+	request, err := c.client.newRequest(ctx, http.MethodGet, endpoint.String(), nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	request.Header.Set("Accept", "application/json")
+
+	response, err = c.client.Call(request, &result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return
+}
+
+// Restore restores a historical version to be the latest version.
+// That is, a new version is created with the content of the historical version.
+// Docs: https://docs.go-atlassian.io/confluence-cloud/content/versions#restore-content-version
+func (c *ContentVersionService) Restore(ctx context.Context, contentID string, payload *models.ContentRestorePayloadScheme,
+	expand []string) (result *models.ContentVersionScheme, response *ResponseScheme, err error) {
+
+	if len(contentID) == 0 {
+		return nil, nil, models.ErrNoContentIDError
+	}
+
+	var endpoint strings.Builder
+	endpoint.WriteString(fmt.Sprintf("wiki/rest/api/content/%v/version", contentID))
+
+	query := url.Values{}
+	if len(expand) != 0 {
+		query.Add("expand", strings.Join(expand, ","))
+	}
+
+	if query.Encode() != "" {
+		endpoint.WriteString(fmt.Sprintf("?%v", query.Encode()))
+	}
+
+	payloadAsReader, err := transformStructToReader(payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	request, err := c.client.newRequest(ctx, http.MethodPost, endpoint.String(), payloadAsReader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	request.Header.Set("Accept", "application/json")
+	request.Header.Set("Content-Type", "application/json")
+
+	response, err = c.client.Call(request, &result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return
+}
+
+// Delete deletes a historical version.
+// This does not delete the changes made to the content in that version, rather the changes for the deleted version
+// are rolled up into the next version. Note, you cannot delete the current version.
+// Docs: https://docs.go-atlassian.io/confluence-cloud/content/versions#delete-content-version
+func (c *ContentVersionService) Delete(ctx context.Context, contentID string, versionNumber int) (response *ResponseScheme, err error) {
+
+	if len(contentID) == 0 {
+		return nil, models.ErrNoContentIDError
+	}
+
+	endpoint := fmt.Sprintf("wiki/rest/api/content/%v/version/%v", contentID, versionNumber)
+
+	request, err := c.client.newRequest(ctx, http.MethodDelete, endpoint, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	request.Header.Set("Accept", "application/json")
+	request.Header.Set("Content-Type", "application/json")
+
+	response, err = c.client.Call(request, nil)
+	if err != nil {
+		return response, err
+	}
+
+	return
+}
diff --git a/confluence/label.go b/confluence/label.go
new file mode 100644
index 0000000..216c1fc
--- /dev/null
+++ b/confluence/label.go
@@ -0,0 +1,44 @@
+package confluence
+
+import (
+	"context"
+	"fmt"
+	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"net/http"
+	"net/url"
+	"strconv"
+)
+
+type LabelService struct{ client *Client }
+
+// Get returns label information and a list of contents associated with the label.
+// Docs: https://docs.go-atlassian.io/confluence-cloud/label#get-label-information
+func (l *LabelService) Get(ctx context.Context, labelName, labelType string, start, limit int) (result *models.LabelDetailsScheme,
+	response *ResponseScheme, err error) {
+
+	if labelName == "" {
+		return nil, nil, models.ErrNoLabelNameError
+	}
+
+	query := url.Values{}
+	query.Add("start", strconv.Itoa(start))
+	query.Add("limit", strconv.Itoa(limit))
+	query.Add("name", labelName)
+	query.Add("type", labelType)
+
+	endpoint := fmt.Sprintf("wiki/rest/api/label?%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
+}
diff --git a/confluence/mocks/get-content-version.json b/confluence/mocks/get-content-version.json
new file mode 100644
index 0000000..41f63c5
--- /dev/null
+++ b/confluence/mocks/get-content-version.json
@@ -0,0 +1,929 @@
+{
+  "by": {
+    "type": "known",
+    "username": "<string>",
+    "userKey": "<string>",
+    "accountId": "<string>",
+    "accountType": "atlassian",
+    "email": "<string>",
+    "publicName": "<string>",
+    "profilePicture": {
+      "path": "<string>",
+      "width": 2154,
+      "height": 2154,
+      "isDefault": true
+    },
+    "displayName": "<string>",
+    "timeZone": "<string>",
+    "isExternalCollaborator": true,
+    "externalCollaborator": true,
+    "operations": [
+      {
+        "operation": "administer",
+        "targetType": "<string>"
+      }
+    ],
+    "details": {
+      "business": {
+        "position": "<string>",
+        "department": "<string>",
+        "location": "<string>"
+      },
+      "personal": {
+        "phone": "<string>",
+        "im": "<string>",
+        "website": "<string>",
+        "email": "<string>"
+      }
+    },
+    "personalSpace": {
+      "id": 2154,
+      "key": "<string>",
+      "name": "<string>",
+      "icon": {
+        "path": "<string>",
+        "width": 2154,
+        "height": 2154,
+        "isDefault": true
+      },
+      "description": {
+        "plain": {
+          "value": "<string>",
+          "representation": "plain",
+          "embeddedContent": [
+            {}
+          ]
+        },
+        "view": {
+          "value": "<string>",
+          "representation": "plain",
+          "embeddedContent": [
+            {}
+          ]
+        },
+        "_expandable": {
+          "view": "<string>",
+          "plain": "<string>"
+        }
+      },
+      "homepage": {
+        "type": "<string>",
+        "status": "<string>"
+      },
+      "type": "<string>",
+      "metadata": {
+        "labels": {
+          "results": [
+            {
+              "prefix": "<string>",
+              "name": "<string>",
+              "id": "<string>",
+              "label": "<string>"
+            }
+          ],
+          "size": 2154
+        },
+        "_expandable": {}
+      },
+      "operations": [
+        {
+          "operation": "administer",
+          "targetType": "<string>"
+        }
+      ],
+      "permissions": [
+        {
+          "operation": {
+            "operation": "administer",
+            "targetType": "<string>"
+          },
+          "anonymousAccess": true,
+          "unlicensedAccess": true
+        }
+      ],
+      "status": "<string>",
+      "settings": {
+        "routeOverrideEnabled": true,
+        "_links": {}
+      },
+      "theme": {
+        "themeKey": "<string>"
+      },
+      "lookAndFeel": {
+        "headings": {
+          "color": "<string>"
+        },
+        "links": {
+          "color": "<string>"
+        },
+        "menus": {
+          "hoverOrFocus": {
+            "backgroundColor": "<string>"
+          },
+          "color": "<string>"
+        },
+        "header": {
+          "backgroundColor": "<string>",
+          "button": {
+            "backgroundColor": "<string>",
+            "color": "<string>"
+          },
+          "primaryNavigation": {
+            "color": "<string>",
+            "hoverOrFocus": {
+              "backgroundColor": "<string>",
+              "color": "<string>"
+            }
+          },
+          "secondaryNavigation": {
+            "color": "<string>",
+            "hoverOrFocus": {
+              "backgroundColor": "<string>",
+              "color": "<string>"
+            }
+          },
+          "search": {
+            "backgroundColor": "<string>",
+            "color": "<string>"
+          }
+        },
+        "content": {},
+        "bordersAndDividers": {
+          "color": "<string>"
+        }
+      },
+      "history": {
+        "createdDate": "<string>"
+      },
+      "_expandable": {
+        "settings": "<string>",
+        "metadata": "<string>",
+        "operations": "<string>",
+        "lookAndFeel": "<string>",
+        "permissions": "<string>",
+        "icon": "<string>",
+        "description": "<string>",
+        "theme": "<string>",
+        "history": "<string>",
+        "homepage": "<string>",
+        "identifiers": "<string>"
+      },
+      "_links": {}
+    },
+    "_expandable": {
+      "operations": "<string>",
+      "details": "<string>",
+      "personalSpace": "<string>"
+    },
+    "_links": {}
+  },
+  "when": "<string>",
+  "friendlyWhen": "<string>",
+  "message": "<string>",
+  "number": 57,
+  "minorEdit": true,
+  "content": {
+    "id": "<string>",
+    "type": "<string>",
+    "status": "<string>",
+    "title": "<string>",
+    "space": {
+      "id": 2154,
+      "key": "<string>",
+      "name": "<string>",
+      "icon": {
+        "path": "<string>",
+        "width": 2154,
+        "height": 2154,
+        "isDefault": true
+      },
+      "description": {
+        "plain": {
+          "value": "<string>",
+          "representation": "plain",
+          "embeddedContent": [
+            {}
+          ]
+        },
+        "view": {
+          "value": "<string>",
+          "representation": "plain",
+          "embeddedContent": [
+            {}
+          ]
+        },
+        "_expandable": {
+          "view": "<string>",
+          "plain": "<string>"
+        }
+      },
+      "type": "<string>",
+      "metadata": {
+        "labels": {
+          "results": [
+            {
+              "prefix": "<string>",
+              "name": "<string>",
+              "id": "<string>",
+              "label": "<string>"
+            }
+          ],
+          "size": 2154
+        },
+        "_expandable": {}
+      },
+      "operations": [
+        {
+          "operation": "administer",
+          "targetType": "<string>"
+        }
+      ],
+      "permissions": [
+        {
+          "operation": {
+            "operation": "administer",
+            "targetType": "<string>"
+          },
+          "anonymousAccess": true,
+          "unlicensedAccess": true
+        }
+      ],
+      "status": "<string>",
+      "settings": {
+        "routeOverrideEnabled": true,
+        "_links": {}
+      },
+      "theme": {
+        "themeKey": "<string>"
+      },
+      "lookAndFeel": {
+        "headings": {
+          "color": "<string>"
+        },
+        "links": {
+          "color": "<string>"
+        },
+        "menus": {
+          "hoverOrFocus": {
+            "backgroundColor": "<string>"
+          },
+          "color": "<string>"
+        },
+        "header": {
+          "backgroundColor": "<string>",
+          "button": {
+            "backgroundColor": "<string>",
+            "color": "<string>"
+          },
+          "primaryNavigation": {
+            "color": "<string>",
+            "hoverOrFocus": {
+              "backgroundColor": "<string>",
+              "color": "<string>"
+            }
+          },
+          "secondaryNavigation": {
+            "color": "<string>",
+            "hoverOrFocus": {
+              "backgroundColor": "<string>",
+              "color": "<string>"
+            }
+          },
+          "search": {
+            "backgroundColor": "<string>",
+            "color": "<string>"
+          }
+        },
+        "content": {},
+        "bordersAndDividers": {
+          "color": "<string>"
+        }
+      },
+      "history": {
+        "createdDate": "<string>",
+        "createdBy": {
+          "type": "known"
+        }
+      },
+      "_expandable": {
+        "settings": "<string>",
+        "metadata": "<string>",
+        "operations": "<string>",
+        "lookAndFeel": "<string>",
+        "permissions": "<string>",
+        "icon": "<string>",
+        "description": "<string>",
+        "theme": "<string>",
+        "history": "<string>",
+        "homepage": "<string>",
+        "identifiers": "<string>"
+      },
+      "_links": {}
+    },
+    "history": {
+      "latest": true,
+      "createdBy": {
+        "type": "known"
+      },
+      "createdDate": "<string>",
+      "lastUpdated": {
+        "when": "<string>",
+        "number": 57,
+        "minorEdit": true
+      },
+      "previousVersion": {
+        "when": "<string>",
+        "number": 57,
+        "minorEdit": true
+      },
+      "contributors": {
+        "publishers": {
+          "userKeys": [
+            "<string>"
+          ]
+        }
+      },
+      "nextVersion": {
+        "when": "<string>",
+        "number": 57,
+        "minorEdit": true
+      },
+      "_expandable": {
+        "lastUpdated": "<string>",
+        "previousVersion": "<string>",
+        "contributors": "<string>",
+        "nextVersion": "<string>"
+      },
+      "_links": {}
+    },
+    "version": {
+      "by": {
+        "type": "known"
+      },
+      "when": "<string>",
+      "friendlyWhen": "<string>",
+      "message": "<string>",
+      "number": 57,
+      "minorEdit": true,
+      "collaborators": {
+        "userKeys": [
+          "<string>"
+        ]
+      },
+      "_expandable": {
+        "content": "<string>",
+        "collaborators": "<string>"
+      },
+      "_links": {},
+      "contentTypeModified": true,
+      "confRev": "<string>",
+      "syncRev": "<string>",
+      "syncRevSource": "<string>"
+    },
+    "ancestors": [],
+    "operations": [
+      {
+        "operation": "administer",
+        "targetType": "<string>"
+      }
+    ],
+    "children": {
+      "attachment": {
+        "results": [],
+        "size": 2154,
+        "_links": {}
+      },
+      "comment": {
+        "results": [],
+        "size": 2154,
+        "_links": {}
+      },
+      "page": {
+        "results": [],
+        "size": 2154,
+        "_links": {}
+      },
+      "_expandable": {
+        "attachment": "<string>",
+        "comment": "<string>",
+        "page": "<string>"
+      },
+      "_links": {}
+    },
+    "childTypes": {
+      "attachment": {
+        "value": true,
+        "_links": {}
+      },
+      "comment": {
+        "value": true,
+        "_links": {}
+      },
+      "page": {
+        "value": true,
+        "_links": {}
+      },
+      "_expandable": {
+        "all": {},
+        "attachment": {},
+        "comment": {},
+        "page": {}
+      }
+    },
+    "descendants": {
+      "attachment": {
+        "results": [],
+        "size": 2154,
+        "_links": {}
+      },
+      "comment": {
+        "results": [],
+        "size": 2154,
+        "_links": {}
+      },
+      "page": {
+        "results": [],
+        "size": 2154,
+        "_links": {}
+      },
+      "_expandable": {
+        "attachment": "<string>",
+        "comment": "<string>",
+        "page": "<string>"
+      },
+      "_links": {}
+    },
+    "container": {},
+    "body": {
+      "view": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "export_view": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "styled_view": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "storage": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "wiki": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "editor": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "editor2": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "anonymous_export_view": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "atlas_doc_format": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "dynamic": {
+        "value": "<string>",
+        "representation": "view",
+        "embeddedContent": [
+          {}
+        ],
+        "webresource": {},
+        "mediaToken": {
+          "collectionIds": [
+            "<string>"
+          ],
+          "contentId": "<string>",
+          "expiryDateTime": "<string>",
+          "fileIds": [
+            "<string>"
+          ],
+          "token": "<string>"
+        },
+        "_expandable": {
+          "content": "<string>",
+          "embeddedContent": "<string>",
+          "webresource": "<string>",
+          "mediaToken": "<string>"
+        },
+        "_links": {}
+      },
+      "_expandable": {
+        "editor": "<string>",
+        "view": "<string>",
+        "export_view": "<string>",
+        "styled_view": "<string>",
+        "storage": "<string>",
+        "editor2": "<string>",
+        "anonymous_export_view": "<string>",
+        "atlas_doc_format": "<string>",
+        "wiki": "<string>",
+        "dynamic": "<string>",
+        "raw": "<string>"
+      }
+    },
+    "restrictions": {
+      "read": {
+        "operation": "administer",
+        "restrictions": {
+          "user": {
+            "results": [
+              {
+                "type": "known"
+              }
+            ]
+          },
+          "group": {
+            "results": [
+              {
+                "type": "group",
+                "name": "<string>"
+              }
+            ],
+            "start": 2154,
+            "limit": 2154,
+            "size": 2154
+          },
+          "_expandable": {
+            "user": "<string>",
+            "group": "<string>"
+          }
+        },
+        "_expandable": {
+          "restrictions": "<string>",
+          "content": "<string>"
+        },
+        "_links": {}
+      },
+      "update": {
+        "operation": "administer",
+        "restrictions": {
+          "user": {
+            "results": [
+              {
+                "type": "known"
+              }
+            ]
+          },
+          "group": {
+            "results": [
+              {
+                "type": "group",
+                "name": "<string>"
+              }
+            ],
+            "start": 2154,
+            "limit": 2154,
+            "size": 2154
+          },
+          "_expandable": {
+            "user": "<string>",
+            "group": "<string>"
+          }
+        },
+        "_expandable": {
+          "restrictions": "<string>",
+          "content": "<string>"
+        },
+        "_links": {}
+      },
+      "_expandable": {
+        "read": "<string>",
+        "update": "<string>"
+      },
+      "_links": {}
+    },
+    "metadata": {
+      "currentuser": {
+        "favourited": {
+          "isFavourite": true,
+          "favouritedDate": "<string>"
+        },
+        "lastmodified": {
+          "version": {
+            "when": "<string>",
+            "number": 57,
+            "minorEdit": true
+          },
+          "friendlyLastModified": "<string>"
+        },
+        "lastcontributed": {
+          "status": "<string>",
+          "when": "<string>"
+        },
+        "viewed": {
+          "lastSeen": "<string>",
+          "friendlyLastSeen": "<string>"
+        },
+        "scheduled": {},
+        "_expandable": {
+          "favourited": "<string>",
+          "lastmodified": "<string>",
+          "lastcontributed": "<string>",
+          "viewed": "<string>",
+          "scheduled": "<string>"
+        }
+      },
+      "properties": {},
+      "frontend": {},
+      "labels": {
+        "results": [
+          {
+            "prefix": "<string>",
+            "name": "<string>",
+            "id": "<string>",
+            "label": "<string>"
+          }
+        ],
+        "start": 2154,
+        "limit": 2154,
+        "size": 2154,
+        "_links": {}
+      }
+    },
+    "macroRenderedOutput": {},
+    "extensions": {},
+    "_expandable": {
+      "childTypes": "<string>",
+      "container": "<string>",
+      "metadata": "<string>",
+      "operations": "<string>",
+      "children": "<string>",
+      "restrictions": "<string>",
+      "history": "<string>",
+      "ancestors": "<string>",
+      "body": "<string>",
+      "version": "<string>",
+      "descendants": "<string>",
+      "space": "<string>",
+      "extensions": "<string>",
+      "schedulePublishDate": "<string>",
+      "macroRenderedOutput": "<string>"
+    },
+    "_links": {}
+  },
+  "collaborators": {
+    "users": [
+      {
+        "type": "known",
+        "username": "<string>",
+        "userKey": "<string>",
+        "accountId": "<string>",
+        "accountType": "atlassian",
+        "email": "<string>",
+        "publicName": "<string>",
+        "profilePicture": {
+          "path": "<string>",
+          "width": 2154,
+          "height": 2154,
+          "isDefault": true
+        },
+        "displayName": "<string>",
+        "timeZone": "<string>",
+        "isExternalCollaborator": true,
+        "externalCollaborator": true,
+        "operations": [
+          {
+            "operation": "administer",
+            "targetType": "<string>"
+          }
+        ],
+        "details": {},
+        "personalSpace": {
+          "key": "<string>",
+          "name": "<string>",
+          "type": "<string>",
+          "status": "<string>",
+          "_expandable": {},
+          "_links": {}
+        },
+        "_expandable": {
+          "operations": "<string>",
+          "details": "<string>",
+          "personalSpace": "<string>"
+        },
+        "_links": {}
+      }
+    ],
+    "userKeys": [
+      "<string>"
+    ],
+    "_links": {}
+  },
+  "_expandable": {
+    "content": "<string>",
+    "collaborators": "<string>"
+  },
+  "_links": {},
+  "contentTypeModified": true,
+  "confRev": "<string>",
+  "syncRev": "<string>",
+  "syncRevSource": "<string>"
+}
\ No newline at end of file
diff --git a/confluence/mocks/get-content-versions.json b/confluence/mocks/get-content-versions.json
new file mode 100644
index 0000000..8dfdf8b
--- /dev/null
+++ b/confluence/mocks/get-content-versions.json
@@ -0,0 +1,196 @@
+{
+  "results": [
+    {
+      "by": {
+        "type": "known",
+        "username": "<string>",
+        "userKey": "<string>",
+        "accountId": "<string>",
+        "accountType": "atlassian",
+        "email": "<string>",
+        "publicName": "<string>",
+        "profilePicture": {
+          "path": "<string>",
+          "width": 2154,
+          "height": 2154,
+          "isDefault": true
+        },
+        "displayName": "<string>",
+        "timeZone": "<string>",
+        "isExternalCollaborator": true,
+        "externalCollaborator": true,
+        "operations": [
+          {
+            "operation": "administer",
+            "targetType": "<string>"
+          }
+        ],
+        "details": {},
+        "personalSpace": {
+          "key": "<string>",
+          "name": "<string>",
+          "type": "<string>",
+          "status": "<string>",
+          "_expandable": {},
+          "_links": {}
+        },
+        "_expandable": {
+          "operations": "<string>",
+          "details": "<string>",
+          "personalSpace": "<string>"
+        },
+        "_links": {}
+      },
+      "when": "<string>",
+      "friendlyWhen": "<string>",
+      "message": "<string>",
+      "number": 57,
+      "minorEdit": true,
+      "content": {
+        "id": "<string>",
+        "type": "<string>",
+        "status": "<string>",
+        "title": "<string>",
+        "space": {
+          "key": "<string>",
+          "name": "<string>",
+          "type": "<string>",
+          "status": "<string>",
+          "_expandable": {},
+          "_links": {}
+        },
+        "history": {
+          "latest": true
+        },
+        "ancestors": [],
+        "operations": [
+          {
+            "operation": "administer",
+            "targetType": "<string>"
+          }
+        ],
+        "children": {},
+        "childTypes": {},
+        "descendants": {},
+        "container": {},
+        "body": {
+          "view": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "export_view": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "styled_view": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "storage": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "wiki": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "editor": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "editor2": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "anonymous_export_view": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "atlas_doc_format": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "dynamic": {
+            "value": "<string>",
+            "representation": "view"
+          },
+          "_expandable": {
+            "editor": "<string>",
+            "view": "<string>",
+            "export_view": "<string>",
+            "styled_view": "<string>",
+            "storage": "<string>",
+            "editor2": "<string>",
+            "anonymous_export_view": "<string>",
+            "atlas_doc_format": "<string>",
+            "wiki": "<string>",
+            "dynamic": "<string>",
+            "raw": "<string>"
+          }
+        },
+        "restrictions": {
+          "read": {
+            "operation": "administer",
+            "_expandable": {},
+            "_links": {}
+          },
+          "update": {
+            "operation": "administer",
+            "_expandable": {},
+            "_links": {}
+          },
+          "_expandable": {
+            "read": "<string>",
+            "update": "<string>"
+          },
+          "_links": {}
+        },
+        "metadata": {},
+        "macroRenderedOutput": {},
+        "extensions": {},
+        "_expandable": {
+          "childTypes": "<string>",
+          "container": "<string>",
+          "metadata": "<string>",
+          "operations": "<string>",
+          "children": "<string>",
+          "restrictions": "<string>",
+          "history": "<string>",
+          "ancestors": "<string>",
+          "body": "<string>",
+          "version": "<string>",
+          "descendants": "<string>",
+          "space": "<string>",
+          "extensions": "<string>",
+          "schedulePublishDate": "<string>",
+          "macroRenderedOutput": "<string>"
+        },
+        "_links": {}
+      },
+      "collaborators": {
+        "users": [
+          {
+            "type": "known"
+          }
+        ],
+        "userKeys": [
+          "<string>"
+        ],
+        "_links": {}
+      },
+      "_expandable": {
+        "content": "<string>",
+        "collaborators": "<string>"
+      },
+      "_links": {},
+      "contentTypeModified": true,
+      "confRev": "<string>",
+      "syncRev": "<string>",
+      "syncRevSource": "<string>"
+    }
+  ],
+  "start": 2154,
+  "limit": 2154,
+  "size": 2154,
+  "_links": {}
+}
\ No newline at end of file
diff --git a/confluence/mocks/get-label-details.json b/confluence/mocks/get-label-details.json
new file mode 100644
index 0000000..754bfcf
--- /dev/null
+++ b/confluence/mocks/get-label-details.json
@@ -0,0 +1,20 @@
+{
+  "label": {
+    "prefix": "<string>",
+    "name": "<string>",
+    "id": "<string>",
+    "label": "<string>"
+  },
+  "associatedContents": {
+    "results": [
+      {
+        "contentType": "page",
+        "contentId": 2154,
+        "title": "<string>"
+      }
+    ],
+    "start": 2154,
+    "limit": 2154,
+    "size": 2154
+  }
+}
\ No newline at end of file
diff --git a/pkg/infra/models/confluence_content.go b/pkg/infra/models/confluence_content.go
index d4129a7..c78e1a3 100644
--- a/pkg/infra/models/confluence_content.go
+++ b/pkg/infra/models/confluence_content.go
@@ -56,27 +56,6 @@ type ContentExtensionScheme struct {
 	FileID               string `json:"fileId,omitempty"`
 }
 
-type ContentVersionScheme struct {
-	By            *ContentUserScheme          `json:"by,omitempty"`
-	Number        int                         `json:"number,omitempty"`
-	When          string                      `json:"when,omitempty"`
-	FriendlyWhen  string                      `json:"friendlyWhen,omitempty"`
-	Message       string                      `json:"message,omitempty"`
-	MinorEdit     bool                        `json:"minorEdit,omitempty"`
-	Content       *ContentScheme              `json:"content,omitempty"`
-	Collaborators *VersionCollaboratorsScheme `json:"collaborators,omitempty"`
-	Expandable    struct {
-		Content       string `json:"content"`
-		Collaborators string `json:"collaborators"`
-	} `json:"_expandable"`
-	ContentTypeModified bool `json:"contentTypeModified"`
-}
-
-type VersionCollaboratorsScheme struct {
-	Users    []*ContentUserScheme `json:"users,omitempty"`
-	UserKeys []string             `json:"userKeys,omitempty"`
-}
-
 type BodyScheme struct {
 	View                *BodyNodeScheme `json:"view"`
 	ExportView          *BodyNodeScheme `json:"export_view"`
diff --git a/pkg/infra/models/confluence_content_version.go b/pkg/infra/models/confluence_content_version.go
new file mode 100644
index 0000000..1db4771
--- /dev/null
+++ b/pkg/infra/models/confluence_content_version.go
@@ -0,0 +1,40 @@
+package models
+
+type ContentVersionPageScheme struct {
+	Results []*ContentVersionScheme `json:"results,omitempty"`
+	Start   int                     `json:"start,omitempty"`
+	Limit   int                     `json:"limit,omitempty"`
+	Size    int                     `json:"size,omitempty"`
+}
+
+type ContentVersionScheme struct {
+	By            *ContentUserScheme          `json:"by,omitempty"`
+	Number        int                         `json:"number,omitempty"`
+	When          string                      `json:"when,omitempty"`
+	FriendlyWhen  string                      `json:"friendlyWhen,omitempty"`
+	Message       string                      `json:"message,omitempty"`
+	MinorEdit     bool                        `json:"minorEdit,omitempty"`
+	Content       *ContentScheme              `json:"content,omitempty"`
+	Collaborators *VersionCollaboratorsScheme `json:"collaborators,omitempty"`
+	Expandable    struct {
+		Content       string `json:"content"`
+		Collaborators string `json:"collaborators"`
+	} `json:"_expandable"`
+	ContentTypeModified bool `json:"contentTypeModified"`
+}
+
+type VersionCollaboratorsScheme struct {
+	Users    []*ContentUserScheme `json:"users,omitempty"`
+	UserKeys []string             `json:"userKeys,omitempty"`
+}
+
+type ContentRestorePayloadScheme struct {
+	OperationKey string                             `json:"operationKey,omitempty"`
+	Params       *ContentRestoreParamsPayloadScheme `json:"params,omitempty"`
+}
+
+type ContentRestoreParamsPayloadScheme struct {
+	VersionNumber int    `json:"versionNumber,omitempty"`
+	Message       string `json:"message,omitempty"`
+	RestoreTitle  bool   `json:"restoreTitle,omitempty"`
+}
diff --git a/pkg/infra/models/confluence_label.go b/pkg/infra/models/confluence_label.go
new file mode 100644
index 0000000..c6372a5
--- /dev/null
+++ b/pkg/infra/models/confluence_label.go
@@ -0,0 +1,19 @@
+package models
+
+type LabelDetailsScheme struct {
+	Label              *ContentLabelScheme               `json:"label"`
+	AssociatedContents *LabelAssociatedContentPageScheme `json:"associatedContents"`
+}
+
+type LabelAssociatedContentPageScheme struct {
+	Results []*LabelAssociatedContentScheme `json:"results,omitempty"`
+	Start   int                             `json:"start,omitempty"`
+	Limit   int                             `json:"limit,omitempty"`
+	Size    int                             `json:"size,omitempty"`
+}
+
+type LabelAssociatedContentScheme struct {
+	ContentType string `json:"contentType,omitempty"`
+	ContentID   int    `json:"contentId,omitempty"`
+	Title       string `json:"title,omitempty"`
+}
diff --git a/pkg/infra/models/errors.go b/pkg/infra/models/errors.go
index 2b53c64..e7e8293 100644
--- a/pkg/infra/models/errors.go
+++ b/pkg/infra/models/errors.go
@@ -37,6 +37,7 @@ var (
 	ErrNoSpaceKeyError              = errors.New("confluence: no space key set")
 	ErrNoContentRestrictionKeyError = errors.New("confluence: no content restriction operation key set")
 	ErrNoConfluenceGroupError       = errors.New("confluence: no group id or name set")
+	ErrNoLabelNameError             = errors.New("confluence: no label name set")
 
 	ErrNoBoardIDError  = errors.New("agile: no board id set")
 	ErrNoFilterIDError = errors.New("agile: no filter id set")


Test patch:
diff --git a/confluence/contentVersion_test.go b/confluence/contentVersion_test.go
new file mode 100644
index 0000000..96c1815
--- /dev/null
+++ b/confluence/contentVersion_test.go
@@ -0,0 +1,642 @@
+package confluence
+
+import (
+	"context"
+	"fmt"
+	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"net/url"
+	"testing"
+)
+
+func Test_Content_Version_Service_Gets(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		contentID          string
+		expand             []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",
+			contentID:          "233838383",
+			expand:             []string{"restrictions.user", "restrictions.group"},
+			start:              0,
+			limit:              50,
+			mockFile:           "./mocks/get-content-versions.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=restrictions.user%2Crestrictions.group&limit=50&start=0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+		},
+
+		{
+			name:               "when the content id is not provided",
+			contentID:          "",
+			expand:             []string{"restrictions.user", "restrictions.group"},
+			start:              0,
+			limit:              50,
+			mockFile:           "./mocks/get-content-versions.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=restrictions.user%2Crestrictions.group&limit=50&start=0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "confluence: no content id set",
+		},
+
+		{
+			name:               "when the context is not provided",
+			contentID:          "233838383",
+			expand:             []string{"restrictions.user", "restrictions.group"},
+			start:              0,
+			limit:              50,
+			mockFile:           "./mocks/get-content-versions.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=restrictions.user%2Crestrictions.group&limit=50&start=0",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "request creation failed: net/http: nil Context",
+		},
+
+		{
+			name:               "when the response status is not correct",
+			contentID:          "233838383",
+			expand:             []string{"restrictions.user", "restrictions.group"},
+			start:              0,
+			limit:              50,
+			mockFile:           "./mocks/get-content-versions.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=restrictions.user%2Crestrictions.group&limit=50&start=0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusBadRequest,
+			wantErr:            true,
+			expectedError:      "request failed. Please analyze the request body for more details. Status Code: 400",
+		},
+
+		{
+			name:               "when the response body is not empty",
+			contentID:          "233838383",
+			expand:             []string{"restrictions.user", "restrictions.group"},
+			start:              0,
+			limit:              50,
+			mockFile:           "./mocks/empty-json.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=restrictions.user%2Crestrictions.group&limit=50&start=0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "unexpected end of JSON input",
+		},
+	}
+	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)
+			}
+
+			service := &ContentVersionService{client: mockClient}
+
+			gotResult, gotResponse, err := service.Gets(testCase.context, testCase.contentID, testCase.expand,
+				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)
+
+				t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				assert.Equal(t, gotResponse.Code, testCase.wantHTTPCodeReturn)
+			}
+		})
+	}
+}
+
+func Test_Content_Version_Service_Get(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		contentID          string
+		expand             []string
+		versionNumber      int
+		mockFile           string
+		wantHTTPMethod     string
+		endpoint           string
+		context            context.Context
+		wantHTTPCodeReturn int
+		wantErr            bool
+		expectedError      string
+	}{
+		{
+			name:               "when the parameters are correct",
+			contentID:          "233838383",
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+		},
+
+		{
+			name:               "when the content id is not provided",
+			contentID:          "",
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "confluence: no content id set",
+		},
+
+		{
+			name:               "when the context is not provided",
+			contentID:          "233838383",
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0?expand=collaborators%2Ccontent",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "request creation failed: net/http: nil Context",
+		},
+
+		{
+			name:               "when the response status is not correct",
+			contentID:          "233838383",
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusBadRequest,
+			wantErr:            true,
+			expectedError:      "request failed. Please analyze the request body for more details. Status Code: 400",
+		},
+
+		{
+			name:               "when the response body is not empty",
+			contentID:          "233838383",
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/empty-json.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "unexpected end of JSON input",
+		},
+	}
+	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)
+			}
+
+			service := &ContentVersionService{client: mockClient}
+
+			gotResult, gotResponse, err := service.Get(testCase.context, testCase.contentID, testCase.versionNumber, testCase.expand)
+
+			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)
+
+				t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				assert.Equal(t, gotResponse.Code, testCase.wantHTTPCodeReturn)
+			}
+		})
+	}
+}
+
+func Test_Content_Version_Service_Restore(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		contentID          string
+		payload            *models.ContentRestorePayloadScheme
+		expand             []string
+		mockFile           string
+		wantHTTPMethod     string
+		endpoint           string
+		context            context.Context
+		wantHTTPCodeReturn int
+		wantErr            bool
+		expectedError      string
+	}{
+		{
+			name:      "when the parameters are correct",
+			contentID: "233838383",
+			payload: &models.ContentRestorePayloadScheme{
+				OperationKey: "restore",
+				Params: &models.ContentRestoreParamsPayloadScheme{
+					VersionNumber: 034,
+					Message:       "message sample :)",
+					RestoreTitle:  true,
+				},
+			},
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+		},
+
+		{
+			name:               "when the paylod is not provided",
+			contentID:          "233838383",
+			payload:            nil,
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "failed to parse the interface pointer, please provide a valid one",
+		},
+
+		{
+			name:      "when the content id is not provided",
+			contentID: "",
+			payload: &models.ContentRestorePayloadScheme{
+				OperationKey: "restore",
+				Params: &models.ContentRestoreParamsPayloadScheme{
+					VersionNumber: 034,
+					Message:       "message sample :)",
+					RestoreTitle:  true,
+				},
+			},
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "confluence: no content id set",
+		},
+
+		{
+			name:      "when the context is not provided",
+			contentID: "233838383",
+			payload: &models.ContentRestorePayloadScheme{
+				OperationKey: "restore",
+				Params: &models.ContentRestoreParamsPayloadScheme{
+					VersionNumber: 034,
+					Message:       "message sample :)",
+					RestoreTitle:  true,
+				},
+			},
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=collaborators%2Ccontent",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "request creation failed: net/http: nil Context",
+		},
+
+		{
+			name:      "when the response status is not correct",
+			contentID: "233838383",
+			payload: &models.ContentRestorePayloadScheme{
+				OperationKey: "restore",
+				Params: &models.ContentRestoreParamsPayloadScheme{
+					VersionNumber: 034,
+					Message:       "message sample :)",
+					RestoreTitle:  true,
+				},
+			},
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/get-content-version.json",
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusBadRequest,
+			wantErr:            true,
+			expectedError:      "request failed. Please analyze the request body for more details. Status Code: 400",
+		},
+
+		{
+			name:      "when the response body is not empty",
+			contentID: "233838383",
+			payload: &models.ContentRestorePayloadScheme{
+				OperationKey: "restore",
+				Params: &models.ContentRestoreParamsPayloadScheme{
+					VersionNumber: 034,
+					Message:       "message sample :)",
+					RestoreTitle:  true,
+				},
+			},
+			expand:             []string{"collaborators", "content"},
+			mockFile:           "./mocks/empty-json.json",
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/wiki/rest/api/content/233838383/version?expand=collaborators%2Ccontent",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "unexpected end of JSON input",
+		},
+	}
+	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)
+			}
+
+			service := &ContentVersionService{client: mockClient}
+
+			gotResult, gotResponse, err := service.Restore(testCase.context, testCase.contentID, testCase.payload, testCase.expand)
+
+			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)
+
+				t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				assert.Equal(t, gotResponse.Code, testCase.wantHTTPCodeReturn)
+			}
+		})
+	}
+}
+
+func Test_Content_Version_Service_Delete(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		contentID          string
+		versionNumber      int
+		mockFile           string
+		wantHTTPMethod     string
+		endpoint           string
+		context            context.Context
+		wantHTTPCodeReturn int
+		wantErr            bool
+		expectedError      string
+	}{
+		{
+			name:               "when the parameters are correct",
+			contentID:          "233838383",
+			wantHTTPMethod:     http.MethodDelete,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusNoContent,
+		},
+
+		{
+			name:               "when the content id is not provided",
+			contentID:          "",
+			wantHTTPMethod:     http.MethodDelete,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            true,
+			expectedError:      "confluence: no content id set",
+		},
+
+		{
+			name:               "when the context is not provided",
+			contentID:          "233838383",
+			wantHTTPMethod:     http.MethodDelete,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            true,
+			expectedError:      "request creation failed: net/http: nil Context",
+		},
+
+		{
+			name:               "when the response status is not correct",
+			contentID:          "233838383",
+			wantHTTPMethod:     http.MethodDelete,
+			endpoint:           "/wiki/rest/api/content/233838383/version/0",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusInternalServerError,
+			wantErr:            true,
+			expectedError:      "request failed. Please analyze the request body for more details. Status Code: 500",
+		},
+	}
+	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)
+			}
+
+			service := &ContentVersionService{client: mockClient}
+
+			gotResponse, err := service.Delete(testCase.context, testCase.contentID, testCase.versionNumber)
+
+			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)
+
+				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)
+
+				t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				assert.Equal(t, gotResponse.Code, testCase.wantHTTPCodeReturn)
+			}
+		})
+	}
+}
diff --git a/confluence/label_test.go b/confluence/label_test.go
new file mode 100644
index 0000000..dcaf33c
--- /dev/null
+++ b/confluence/label_test.go
@@ -0,0 +1,169 @@
+package confluence
+
+import (
+	"context"
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"net/url"
+	"testing"
+)
+
+func Test_Label_Service_Get(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		labelName          string
+		labelType          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,
+			labelName:          "tracking",
+			labelType:          "page",
+			mockFile:           "./mocks/get-label-details.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/label?limit=50&name=tracking&start=0&type=page",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+		},
+
+		{
+			name:               "when the label name is not provided",
+			start:              0,
+			limit:              50,
+			labelName:          "",
+			labelType:          "page",
+			mockFile:           "./mocks/get-label-details.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/label?limit=50&name=tracking&start=0&type=page",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "confluence: no label name set",
+		},
+
+		{
+			name:               "when the context is not provided",
+			start:              0,
+			limit:              50,
+			labelName:          "tracking",
+			labelType:          "page",
+			mockFile:           "./mocks/get-label-details.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/label?limit=50&name=tracking&start=0&type=page",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "request creation failed: net/http: nil Context",
+		},
+
+		{
+			name:               "when the response status is not correct",
+			start:              0,
+			limit:              50,
+			labelName:          "tracking",
+			labelType:          "page",
+			mockFile:           "./mocks/get-label-details.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/label?limit=50&name=tracking&start=0&type=page",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusBadRequest,
+			wantErr:            true,
+			expectedError:      "request failed. Please analyze the request body for more details. Status Code: 400",
+		},
+
+		{
+			name:               "when the response body is not empty",
+			start:              0,
+			limit:              50,
+			labelName:          "tracking",
+			labelType:          "page",
+			mockFile:           "./mocks/empty-json.json",
+			wantHTTPMethod:     http.MethodGet,
+			endpoint:           "/wiki/rest/api/label?limit=50&name=tracking&start=0&type=page",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusOK,
+			wantErr:            true,
+			expectedError:      "unexpected end of JSON input",
+		},
+	}
+	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)
+			}
+
+			service := &LabelService{client: mockClient}
+
+			gotResult, gotResponse, err := service.Get(testCase.context, testCase.labelName, testCase.labelType,
+				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)
+
+				t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				assert.Equal(t, gotResponse.Code, testCase.wantHTTPCodeReturn)
+			}
+		})
+	}
+}