Skip to content
This repository was archived by the owner on Dec 10, 2024. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: xanzy/go-gitlab
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.114.0
Choose a base ref
...
head repository: xanzy/go-gitlab
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.115.0
Choose a head ref

Commits on Nov 9, 2024

  1. Add support for the project markdown uploads API

    heidiberry committed Nov 9, 2024
    Copy the full SHA
    24995ab View commit details

Commits on Nov 21, 2024

  1. Verified

    This commit was signed with the committer’s verified signature.
    Copy the full SHA
    2a04814 View commit details
  2. Verified

    This commit was signed with the committer’s verified signature.
    Copy the full SHA
    3440b95 View commit details
  3. feat: add dependency list export client service (#2063)

    * feat: add dependency list client service
    
    * add missing line endings
    
    * rename client service to match endpoint name
    
    * fix and refactor
    
    * Fix casing in comment to match struct name
    
    * fix default export type and make DownloadDependencyListExport return an io.Reader
    
    * fix inconsistent indentation in commented code example
    
    * Update dependency_list_export.go
    
    ---------
    
    Co-authored-by: Timo Furrer <tuxtimo@gmail.com>
    lmphil and timofurrer authored Nov 21, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    8186bd9 View commit details

Commits on Nov 22, 2024

  1. Merge pull request #2058 from heidiberry/project-markdown-uploads

    Add support for the project markdown uploads API
    RicePatrick authored Nov 22, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    42949f8 View commit details

Commits on Nov 25, 2024

  1. docs: update example usages by removing deprecated ioutil

    alexandear committed Nov 25, 2024
    Copy the full SHA
    3838beb View commit details
  2. chore: replace log.Fatal with t.Fatal

    alexandear committed Nov 25, 2024
    Copy the full SHA
    378abcc View commit details
  3. chore: fix golangci-lint warnings

    alexandear committed Nov 25, 2024
    Copy the full SHA
    3a4e612 View commit details
  4. Merge pull request #2068 from alexandear/docs/ioutil-deprecated

    docs: update example usages by removing deprecated ioutil
    RicePatrick authored Nov 25, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    e884d22 View commit details
  5. Merge pull request #2066 from alexandear/chore/t-fatal

    chore: replace log.Fatal with t.Fatal in tests
    RicePatrick authored Nov 25, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c1ce518 View commit details

Commits on Nov 26, 2024

  1. Merge pull request #2067 from alexandear/chore/remove-megacheck

    chore: fix golangci-lint warnings
    RicePatrick authored Nov 26, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    9851eab View commit details

Commits on Nov 27, 2024

  1. add 'name' and 'description' to project hooks structs

    GitLab 17.x API now supports 'name' and 'description' fields
    
    Added fields and updates tests.
    cgahlon committed Nov 27, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    cgahlon Christopher Gahlon
    Copy the full SHA
    1d3decd View commit details

Commits on Nov 29, 2024

  1. remove omitempty from main struct used for reads

    cgahlon committed Nov 29, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    cgahlon Christopher Gahlon
    Copy the full SHA
    f19511b View commit details
  2. Merge pull request #2069 from cgahlon/cgahlon/add-name-and-descriptio…

    …n-to-project-hooks
    
    add 'name' and 'description' to project hooks structs
    RicePatrick authored Nov 29, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    35c2003 View commit details

Commits on Dec 2, 2024

  1. Verified

    This commit was signed with the committer’s verified signature.
    Copy the full SHA
    4762cde View commit details
  2. Verified

    This commit was signed with the committer’s verified signature.
    Copy the full SHA
    0cb05aa View commit details

Commits on Dec 4, 2024

  1. Add options to CreateServiceAccountUser

    Signed-off-by: Ilya Savitsky <ilya.savitsky@codethink.co.uk>
    ipsavitsky committed Dec 4, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    ipsavitsky Ilya Savitsky
    Copy the full SHA
    6ba9ee4 View commit details
  2. Add comment to CreateServiceAccountUserOptions

    RicePatrick authored Dec 4, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c3da1c2 View commit details
  3. Merge pull request #2071 from ipsavitsky/ipsavitsky/create-service-ac…

    …count-user-parameters
    
    Add options to CreateServiceAccountUser
    RicePatrick authored Dec 4, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    01aa9b5 View commit details

Commits on Dec 5, 2024

  1. Deprecate module and its exported client type and constructor functions

    This change set deprecates this module and its exported `Client` type
    and its constructor functions: `NewClient`, `NewBasicAuthClient`,
    `NewJobClient` and `NewOAuthClient`.
    
    Use the new module at https://gitlab.com/gitlab-org/client-go
    timofurrer committed Dec 5, 2024

    Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    2526565 View commit details
  2. Deprecate github.com/xanzy/go-gitlab in favor of gitlab.com/gitlab-or…

    …g/client-go in README
    timofurrer committed Dec 5, 2024

    Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    820d428 View commit details

Commits on Dec 8, 2024

  1. Fix model for get, create and update project variable

    GitLab has inconsistent naming of the filed for hidden variables.
    It seems for keeping UI consistant, GitLab chose to introduce different
    naming for the same field. This could have been easy handled in UI by
    setting two variables on API request instead of introducing combined
    field for UI.
    
    I have added 2 test cases for create and update project API as per the
    gitlab.com API that I manually tested and added similar unit tests.
    yogeshlonkar committed Dec 8, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    Copy the full SHA
    44d69ec View commit details
  2. Fix model for get, create and update group variable

    GitLab has inconsistent naming of the filed for hidden variables.
    It seems for keeping UI consistent, GitLab chose to introduce different
    naming for the same field. This could have been easy handled in UI by
    setting two variables on API request instead of introducing combined
    field for UI.
    
    I have added 2 test cases for create and update project API as per the
    gitlab.com API that I also manually tested.
    yogeshlonkar committed Dec 8, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    Copy the full SHA
    5146c24 View commit details

Commits on Dec 9, 2024

  1. Merge pull request #2065 from yogeshlonkar/feat/hidden-variables-on-p…

    …rojects
    
    Add support for hidden field to project variables
    RicePatrick authored Dec 9, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    5626c64 View commit details
  2. Merge pull request #2064 from yogeshlonkar/main

    Add support for hidden field to group variables
    RicePatrick authored Dec 9, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    7f944b7 View commit details

Commits on Dec 10, 2024

  1. Merge pull request #2075 from xanzy/github/migrate-to-gitlab

    Deprecate this project in favor of https://gitlab.com/gitlab-org/api/client-go
    timofurrer authored Dec 10, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    bbe76a5 View commit details
2 changes: 1 addition & 1 deletion .github/workflows/lint_and_test.yml
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v4

- name: Lint package
uses: golangci/golangci-lint-action@v5
uses: golangci/golangci-lint-action@v6
with:
version: latest

6 changes: 5 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
@@ -19,6 +19,10 @@ linters-settings:
locale: US
ignore-words:
- noteable
revive:
enable-all-rules: false
rules:
- name: deep-exit

linters:
enable:
@@ -30,10 +34,10 @@ linters:
- gosimple
- govet
- ineffassign
- megacheck
- misspell
- nakedret
- nolintlint
- revive
- staticcheck
- typecheck
- unconvert
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
# go-gitlab
# (deprecated) go-gitlab (migrated to https://gitlab.com/gitlab-org/api/client-go)

🚧 **Project moved to https://gitlab.com/gitlab-org/api/client-go** 🚧

This package, `github.com/xanzy/go-gitlab`, has been moved to
[`gitlab.com/gitlab-org/api/client-go`](https://gitlab.com/gitlab-org/api/client-go).

The project will continue to be a primarily community-maintained project,
more about it [here](https://gitlab.com/gitlab-org/client.go/-/blob/main/README.md#maintenance).

**References**:

- [GitLab Project](https://gitlab.com/gitlab-org/api/client-go)
- [Issue Tracker](https://gitlab.com/gitlab-org/api/client-go/-/issues)

## Migration Steps

- Replace `github.com/xanzy/go-gitlab` with `gitlab.com/gitlab-org/api/client-go` in your code base.
- Profit 🎉
- *(the code is fully backwards-compatible, no breaking changes are expected)*

<details><summary>Former README contents</summary>

A GitLab API client enabling Go programs to interact with GitLab in a simple and uniform way

@@ -26,6 +47,7 @@ to add new and/or missing endpoints. Currently, the following services are suppo
- [x] Commits
- [x] Container Registry
- [x] Custom Attributes
- [x] Dependency List Export
- [x] Deploy Keys
- [x] Deployments
- [x] Discussions (threaded comments)
@@ -206,3 +228,5 @@ Contributions are always welcome. For more information, check out the [contribut
## License

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>

</details>
122 changes: 122 additions & 0 deletions dependency_list_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package gitlab

import (
"bytes"
"fmt"
"io"
"net/http"
)

type DependencyListExportService struct {
client *Client
}

// CreateDependencyListExportOptions represents the available CreateDependencyListExport()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/dependency_list_export.html#create-a-pipeline-level-dependency-list-export
type CreateDependencyListExportOptions struct {
ExportType *string `url:"export_type" json:"export_type"`
}

// DependencyListExport represents a request for a GitLab project's dependency list.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/dependency_list_export.html#create-a-pipeline-level-dependency-list-export
type DependencyListExport struct {
ID int `json:"id"`
HasFinished bool `json:"has_finished"`
Self string `json:"self"`
Download string `json:"download"`
}

const defaultExportType = "sbom"

// CreateDependencyListExport creates a new CycloneDX JSON export for all the project dependencies
// detected in a pipeline.
//
// If an authenticated user does not have permission to read_dependency, this request returns a 403
// Forbidden status code.
//
// SBOM exports can be only accessed by the export’s author.
//
// GitLab docs:
// https://docs.gitlab.com/ee/api/dependency_list_export.html#create-a-pipeline-level-dependency-list-export
func (s *DependencyListExportService) CreateDependencyListExport(pipelineID int, opt *CreateDependencyListExportOptions, options ...RequestOptionFunc) (*DependencyListExport, *Response, error) {
// POST /pipelines/:id/dependency_list_exports
createExportPath := fmt.Sprintf("pipelines/%d/dependency_list_exports", pipelineID)

if opt == nil {
opt = &CreateDependencyListExportOptions{}
}
if opt.ExportType == nil {
opt.ExportType = Ptr(defaultExportType)
}

req, err := s.client.NewRequest(http.MethodPost, createExportPath, opt, options)
if err != nil {
return nil, nil, err
}

export := new(DependencyListExport)
resp, err := s.client.Do(req, &export)
if err != nil {
return nil, resp, err
}

return export, resp, nil
}

// GetDependencyListExport gets metadata about a single dependency list export.
//
// GitLab docs:
// https://docs.gitlab.com/ee/api/dependency_list_export.html#get-single-dependency-list-export
func (s *DependencyListExportService) GetDependencyListExport(id int, options ...RequestOptionFunc) (*DependencyListExport, *Response, error) {
// GET /dependency_list_exports/:id
getExportPath := fmt.Sprintf("dependency_list_exports/%d", id)

req, err := s.client.NewRequest(http.MethodGet, getExportPath, nil, options)
if err != nil {
return nil, nil, err
}

export := new(DependencyListExport)
resp, err := s.client.Do(req, &export)
if err != nil {
return nil, resp, err
}

return export, resp, nil
}

// DownloadDependencyListExport downloads a single dependency list export.
//
// The github.com/CycloneDX/cyclonedx-go package can be used to parse the data from the returned io.Reader.
//
// sbom := new(cdx.BOM)
// decoder := cdx.NewBOMDecoder(reader, cdx.BOMFileFormatJSON)
//
// if err = decoder.Decode(sbom); err != nil {
// panic(err)
// }
//
// GitLab docs:
// https://docs.gitlab.com/ee/api/dependency_list_export.html#download-dependency-list-export
func (s *DependencyListExportService) DownloadDependencyListExport(id int, options ...RequestOptionFunc) (io.Reader, *Response, error) {
// GET /dependency_list_exports/:id/download
downloadExportPath := fmt.Sprintf("dependency_list_exports/%d/download", id)

req, err := s.client.NewRequest(http.MethodGet, downloadExportPath, nil, options)
if err != nil {
return nil, nil, err
}

var sbomBuffer bytes.Buffer
resp, err := s.client.Do(req, &sbomBuffer)
if err != nil {
return nil, resp, err
}

return &sbomBuffer, resp, nil
}
85 changes: 85 additions & 0 deletions dependency_list_export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package gitlab

import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCreateDependencyListExport(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/pipelines/1234/dependency_list_exports", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPost)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)

var content CreateDependencyListExportOptions
err = json.Unmarshal(body, &content)
require.NoError(t, err)

assert.Equal(t, "sbom", *content.ExportType)
mustWriteHTTPResponse(t, w, "testdata/create_dependency_list_export.json")
})

d := &CreateDependencyListExportOptions{
ExportType: Ptr("sbom"),
}

export, _, err := client.DependencyListExport.CreateDependencyListExport(1234, d)
require.NoError(t, err)

want := &DependencyListExport{
ID: 5678,
HasFinished: false,
Self: "http://gitlab.example.com/api/v4/dependency_list_exports/5678",
Download: "http://gitlab.example.com/api/v4/dependency_list_exports/5678/download",
}
require.Equal(t, want, export)
}

func TestGetDependencyListExport(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/dependency_list_exports/5678", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
mustWriteHTTPResponse(t, w, "testdata/get_dependency_list_export.json")
})

export, _, err := client.DependencyListExport.GetDependencyListExport(5678)
require.NoError(t, err)

want := &DependencyListExport{
ID: 5678,
HasFinished: true,
Self: "http://gitlab.example.com/api/v4/dependency_list_exports/5678",
Download: "http://gitlab.example.com/api/v4/dependency_list_exports/5678/download",
}
require.Equal(t, want, export)
}

func TestDownloadDependencyListExport(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/dependency_list_exports/5678/download", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
mustWriteHTTPResponse(t, w, "testdata/download_dependency_list_export.json")
})

sbomReader, _, err := client.DependencyListExport.DownloadDependencyListExport(5678)
require.NoError(t, err)

expectedSbom, err := os.ReadFile("testdata/download_dependency_list_export.json")
require.NoError(t, err)

var want bytes.Buffer
want.Write(expectedSbom)

require.Equal(t, &want, sbomReader)
}
11 changes: 5 additions & 6 deletions environments_test.go
Original file line number Diff line number Diff line change
@@ -19,7 +19,6 @@ package gitlab
import (
"encoding/json"
"fmt"
"log"
"net/http"
"reflect"
"testing"
@@ -67,7 +66,7 @@ func TestListEnvironments(t *testing.T) {

envs, _, err := client.Environments.ListEnvironments(1, &ListEnvironmentsOptions{Name: Ptr("review/fix-foo"), ListOptions: ListOptions{Page: 1, PerPage: 10}})
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

createdAtWant, _ := time.Parse(timeLayout, "2013-10-02T10:12:29Z")
@@ -218,7 +217,7 @@ func TestCreateEnvironment(t *testing.T) {
FluxResourcePath: Ptr("HelmRelease/flux-system"),
})
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

createdAtWant, _ := time.Parse(timeLayout, "2013-10-02T10:12:29Z")
@@ -294,7 +293,7 @@ func TestEditEnvironment(t *testing.T) {
FluxResourcePath: Ptr("HelmRelease/flux-system"),
})
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

createdAtWant, _ := time.Parse(timeLayout, "2013-10-02T10:12:29Z")
@@ -336,7 +335,7 @@ func TestDeleteEnvironment(t *testing.T) {
})
_, err := client.Environments.DeleteEnvironment(1, 1)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

@@ -357,7 +356,7 @@ func TestStopEnvironment(t *testing.T) {
})
_, _, err := client.Environments.StopEnvironment(1, 1, &StopEnvironmentOptions{})
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

11 changes: 5 additions & 6 deletions epics_test.go
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ package gitlab

import (
"fmt"
"log"
"net/http"
"reflect"
"testing"
@@ -34,7 +33,7 @@ func TestGetEpic(t *testing.T) {

epic, _, err := client.Epics.GetEpic("7", 8)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Epic{
@@ -58,7 +57,7 @@ func TestDeleteEpic(t *testing.T) {

_, err := client.Epics.DeleteEpic("7", 8)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

@@ -78,7 +77,7 @@ func TestListGroupEpics(t *testing.T) {

epics, _, err := client.Epics.ListGroupEpics("7", listGroupEpics)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Epic{{
@@ -108,7 +107,7 @@ func TestCreateEpic(t *testing.T) {

epic, _, err := client.Epics.CreateEpic("7", createEpicOptions)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Epic{
@@ -138,7 +137,7 @@ func TestUpdateEpic(t *testing.T) {

epic, _, err := client.Epics.UpdateEpic("7", 8, updateEpicOptions)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Epic{
6 changes: 3 additions & 3 deletions event_parsing.go
Original file line number Diff line number Diff line change
@@ -91,7 +91,7 @@ func HookEventType(r *http.Request) EventType {
// Example usage:
//
// func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// payload, err := ioutil.ReadAll(r.Body)
// payload, err := io.ReadAll(r.Body)
// if err != nil { ... }
// event, err := gitlab.ParseHook(gitlab.HookEventType(r), payload)
// if err != nil { ... }
@@ -119,7 +119,7 @@ func ParseHook(eventType EventType, payload []byte) (event interface{}, err erro
// Example usage:
//
// func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// payload, err := ioutil.ReadAll(r.Body)
// payload, err := io.ReadAll(r.Body)
// if err != nil { ... }
// event, err := gitlab.ParseSystemhook(payload)
// if err != nil { ... }
@@ -203,7 +203,7 @@ func WebhookEventType(r *http.Request) EventType {
// Example usage:
//
// func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// payload, err := ioutil.ReadAll(r.Body)
// payload, err := io.ReadAll(r.Body)
// if err != nil { ... }
// event, err := gitlab.ParseWebhook(gitlab.HookEventType(r), payload)
// if err != nil { ... }
48 changes: 24 additions & 24 deletions event_parsing_systemhook_test.go
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ import (
)

func TestParseSystemhookPush(t *testing.T) {
payload := loadFixture("testdata/systemhooks/push.json")
payload := loadFixture(t, "testdata/systemhooks/push.json")

parsedEvent, err := ParseSystemhook(payload)
if err != nil {
@@ -38,7 +38,7 @@ func TestParseSystemhookPush(t *testing.T) {
}

func TestParseSystemhookTagPush(t *testing.T) {
payload := loadFixture("testdata/systemhooks/tag_push.json")
payload := loadFixture(t, "testdata/systemhooks/tag_push.json")

parsedEvent, err := ParseSystemhook(payload)
if err != nil {
@@ -53,7 +53,7 @@ func TestParseSystemhookTagPush(t *testing.T) {
}

func TestParseSystemhookMergeRequest(t *testing.T) {
payload := loadFixture("testdata/systemhooks/merge_request.json")
payload := loadFixture(t, "testdata/systemhooks/merge_request.json")

parsedEvent, err := ParseSystemhook(payload)
if err != nil {
@@ -68,7 +68,7 @@ func TestParseSystemhookMergeRequest(t *testing.T) {
}

func TestParseSystemhookRepositoryUpdate(t *testing.T) {
payload := loadFixture("testdata/systemhooks/repository_update.json")
payload := loadFixture(t, "testdata/systemhooks/repository_update.json")

parsedEvent, err := ParseSystemhook(payload)
if err != nil {
@@ -87,11 +87,11 @@ func TestParseSystemhookProject(t *testing.T) {
event string
payload []byte
}{
{"project_create", loadFixture("testdata/systemhooks/project_create.json")},
{"project_update", loadFixture("testdata/systemhooks/project_update.json")},
{"project_destroy", loadFixture("testdata/systemhooks/project_destroy.json")},
{"project_transfer", loadFixture("testdata/systemhooks/project_transfer.json")},
{"project_rename", loadFixture("testdata/systemhooks/project_rename.json")},
{"project_create", loadFixture(t, "testdata/systemhooks/project_create.json")},
{"project_update", loadFixture(t, "testdata/systemhooks/project_update.json")},
{"project_destroy", loadFixture(t, "testdata/systemhooks/project_destroy.json")},
{"project_transfer", loadFixture(t, "testdata/systemhooks/project_transfer.json")},
{"project_rename", loadFixture(t, "testdata/systemhooks/project_rename.json")},
}
for _, tc := range tests {
t.Run(tc.event, func(t *testing.T) {
@@ -113,9 +113,9 @@ func TestParseSystemhookGroup(t *testing.T) {
event string
payload []byte
}{
{"group_create", loadFixture("testdata/systemhooks/group_create.json")},
{"group_destroy", loadFixture("testdata/systemhooks/group_destroy.json")},
{"group_rename", loadFixture("testdata/systemhooks/group_rename.json")},
{"group_create", loadFixture(t, "testdata/systemhooks/group_create.json")},
{"group_destroy", loadFixture(t, "testdata/systemhooks/group_destroy.json")},
{"group_rename", loadFixture(t, "testdata/systemhooks/group_rename.json")},
}
for _, tc := range tests {
t.Run(tc.event, func(t *testing.T) {
@@ -137,10 +137,10 @@ func TestParseSystemhookUser(t *testing.T) {
event string
payload []byte
}{
{"user_create", loadFixture("testdata/systemhooks/user_create.json")},
{"user_destroy", loadFixture("testdata/systemhooks/user_destroy.json")},
{"user_rename", loadFixture("testdata/systemhooks/user_rename.json")},
{"user_failed_login", loadFixture("testdata/systemhooks/user_failed_login.json")},
{"user_create", loadFixture(t, "testdata/systemhooks/user_create.json")},
{"user_destroy", loadFixture(t, "testdata/systemhooks/user_destroy.json")},
{"user_rename", loadFixture(t, "testdata/systemhooks/user_rename.json")},
{"user_failed_login", loadFixture(t, "testdata/systemhooks/user_failed_login.json")},
}
for _, tc := range tests {
t.Run(tc.event, func(t *testing.T) {
@@ -162,9 +162,9 @@ func TestParseSystemhookUserGroup(t *testing.T) {
event string
payload []byte
}{
{"user_add_to_group", loadFixture("testdata/systemhooks/user_add_to_group.json")},
{"user_remove_from_group", loadFixture("testdata/systemhooks/user_remove_from_group.json")},
{"user_update_for_group", loadFixture("testdata/systemhooks/user_update_for_group.json")},
{"user_add_to_group", loadFixture(t, "testdata/systemhooks/user_add_to_group.json")},
{"user_remove_from_group", loadFixture(t, "testdata/systemhooks/user_remove_from_group.json")},
{"user_update_for_group", loadFixture(t, "testdata/systemhooks/user_update_for_group.json")},
}
for _, tc := range tests {
t.Run(tc.event, func(t *testing.T) {
@@ -186,9 +186,9 @@ func TestParseSystemhookUserTeam(t *testing.T) {
event string
payload []byte
}{
{"user_add_to_team", loadFixture("testdata/systemhooks/user_add_to_team.json")},
{"user_remove_from_team", loadFixture("testdata/systemhooks/user_remove_from_team.json")},
{"user_update_for_team", loadFixture("testdata/systemhooks/user_update_for_team.json")},
{"user_add_to_team", loadFixture(t, "testdata/systemhooks/user_add_to_team.json")},
{"user_remove_from_team", loadFixture(t, "testdata/systemhooks/user_remove_from_team.json")},
{"user_update_for_team", loadFixture(t, "testdata/systemhooks/user_update_for_team.json")},
}
for _, tc := range tests {
t.Run(tc.event, func(t *testing.T) {
@@ -206,11 +206,11 @@ func TestParseSystemhookUserTeam(t *testing.T) {
}

func TestParseHookSystemHook(t *testing.T) {
parsedEvent1, err := ParseHook("System Hook", loadFixture("testdata/systemhooks/merge_request.json"))
parsedEvent1, err := ParseHook("System Hook", loadFixture(t, "testdata/systemhooks/merge_request.json"))
if err != nil {
t.Errorf("Error parsing build hook: %s", err)
}
parsedEvent2, err := ParseSystemhook(loadFixture("testdata/systemhooks/merge_request.json"))
parsedEvent2, err := ParseSystemhook(loadFixture(t, "testdata/systemhooks/merge_request.json"))
if err != nil {
t.Errorf("Error parsing build hook: %s", err)
}
40 changes: 20 additions & 20 deletions event_parsing_webhook_test.go
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ func TestWebhookEventToken(t *testing.T) {
}

func TestParseBuildHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/build.json")
raw := loadFixture(t, "testdata/webhooks/build.json")

parsedEvent, err := ParseWebhook("Build Hook", raw)
if err != nil {
@@ -85,7 +85,7 @@ func TestParseBuildHook(t *testing.T) {
}

func TestParseCommitCommentHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/note_commit.json")
raw := loadFixture(t, "testdata/webhooks/note_commit.json")

parsedEvent, err := ParseWebhook("Note Hook", raw)
if err != nil {
@@ -115,7 +115,7 @@ func TestParseCommitCommentHook(t *testing.T) {
}

func TestParseFeatureFlagHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/feature_flag.json")
raw := loadFixture(t, "testdata/webhooks/feature_flag.json")

parsedEvent, err := ParseWebhook("Feature Flag Hook", raw)
if err != nil {
@@ -161,7 +161,7 @@ func TestParseFeatureFlagHook(t *testing.T) {
}

func TestParseGroupResourceAccessTokenHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/resource_access_token_group.json")
raw := loadFixture(t, "testdata/webhooks/resource_access_token_group.json")

parsedEvent, err := ParseWebhook("Resource Access Token Hook", raw)
if err != nil {
@@ -181,19 +181,19 @@ func TestParseGroupResourceAccessTokenHook(t *testing.T) {
}

func TestParseHookWebHook(t *testing.T) {
parsedEvent1, err := ParseHook("Merge Request Hook", loadFixture("testdata/webhooks/merge_request.json"))
parsedEvent1, err := ParseHook("Merge Request Hook", loadFixture(t, "testdata/webhooks/merge_request.json"))
if err != nil {
t.Errorf("Error parsing build hook: %s", err)
}
parsedEvent2, err := ParseWebhook("Merge Request Hook", loadFixture("testdata/webhooks/merge_request.json"))
parsedEvent2, err := ParseWebhook("Merge Request Hook", loadFixture(t, "testdata/webhooks/merge_request.json"))
if err != nil {
t.Errorf("Error parsing build hook: %s", err)
}
assert.Equal(t, parsedEvent1, parsedEvent2)
}

func TestParseIssueCommentHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/note_issue.json")
raw := loadFixture(t, "testdata/webhooks/note_issue.json")

parsedEvent, err := ParseWebhook("Note Hook", raw)
if err != nil {
@@ -224,7 +224,7 @@ func TestParseIssueCommentHook(t *testing.T) {
}

func TestParseIssueHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/issue.json")
raw := loadFixture(t, "testdata/webhooks/issue.json")

parsedEvent, err := ParseWebhook("Issue Hook", raw)
if err != nil {
@@ -263,7 +263,7 @@ func TestParseIssueHook(t *testing.T) {
}

func TestParseMergeRequestCommentHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/note_merge_request.json")
raw := loadFixture(t, "testdata/webhooks/note_merge_request.json")

parsedEvent, err := ParseWebhook("Note Hook", raw)
if err != nil {
@@ -298,7 +298,7 @@ func TestParseMergeRequestCommentHook(t *testing.T) {
}

func TestParseMemberHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/member.json")
raw := loadFixture(t, "testdata/webhooks/member.json")

parsedEvent, err := ParseWebhook("Member Hook", raw)
if err != nil {
@@ -316,7 +316,7 @@ func TestParseMemberHook(t *testing.T) {
}

func TestParseMergeRequestHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/merge_request.json")
raw := loadFixture(t, "testdata/webhooks/merge_request.json")

parsedEvent, err := ParseWebhook("Merge Request Hook", raw)
if err != nil {
@@ -351,7 +351,7 @@ func TestParseMergeRequestHook(t *testing.T) {
}

func TestParsePipelineHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/pipeline.json")
raw := loadFixture(t, "testdata/webhooks/pipeline.json")

parsedEvent, err := ParseWebhook("Pipeline Hook", raw)
if err != nil {
@@ -385,7 +385,7 @@ func TestParsePipelineHook(t *testing.T) {
}

func TestParseProjectResourceAccessTokenHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/resource_access_token_project.json")
raw := loadFixture(t, "testdata/webhooks/resource_access_token_project.json")

parsedEvent, err := ParseWebhook("Resource Access Token Hook", raw)
if err != nil {
@@ -405,7 +405,7 @@ func TestParseProjectResourceAccessTokenHook(t *testing.T) {
}

func TestParsePushHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/push.json")
raw := loadFixture(t, "testdata/webhooks/push.json")

parsedEvent, err := ParseWebhook("Push Hook", raw)
if err != nil {
@@ -439,7 +439,7 @@ func TestParsePushHook(t *testing.T) {
}

func TestParseReleaseHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/release.json")
raw := loadFixture(t, "testdata/webhooks/release.json")

parsedEvent, err := ParseWebhook("Release Hook", raw)
if err != nil {
@@ -461,7 +461,7 @@ func TestParseReleaseHook(t *testing.T) {
}

func TestParseServiceWebHook(t *testing.T) {
parsedEvent, err := ParseWebhook("Service Hook", loadFixture("testdata/webhooks/service_merge_request.json"))
parsedEvent, err := ParseWebhook("Service Hook", loadFixture(t, "testdata/webhooks/service_merge_request.json"))
if err != nil {
t.Errorf("Error parsing service hook merge request: %s", err)
}
@@ -484,7 +484,7 @@ func TestParseServiceWebHook(t *testing.T) {
}

func TestParseSnippetCommentHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/note_snippet.json")
raw := loadFixture(t, "testdata/webhooks/note_snippet.json")

parsedEvent, err := ParseWebhook("Note Hook", raw)
if err != nil {
@@ -514,7 +514,7 @@ func TestParseSnippetCommentHook(t *testing.T) {
}

func TestParseSubGroupHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/subgroup.json")
raw := loadFixture(t, "testdata/webhooks/subgroup.json")

parsedEvent, err := ParseWebhook("Subgroup Hook", raw)
if err != nil {
@@ -532,7 +532,7 @@ func TestParseSubGroupHook(t *testing.T) {
}

func TestParseTagHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/tag_push.json")
raw := loadFixture(t, "testdata/webhooks/tag_push.json")

parsedEvent, err := ParseWebhook("Tag Push Hook", raw)
if err != nil {
@@ -566,7 +566,7 @@ func TestParseTagHook(t *testing.T) {
}

func TestParseWikiPageHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/wiki_page.json")
raw := loadFixture(t, "testdata/webhooks/wiki_page.json")

parsedEvent, err := ParseWebhook("Wiki Page Hook", raw)
if err != nil {
36 changes: 18 additions & 18 deletions event_webhook_types_test.go
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ const (
)

func TestBuildEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/build.json")
jsonObject := loadFixture(t, "testdata/webhooks/build.json")

var event *BuildEvent
err := json.Unmarshal(jsonObject, &event)
@@ -65,7 +65,7 @@ func TestBuildEventUnmarshal(t *testing.T) {
}

func TestCommitCommentEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/note_commit.json")
jsonObject := loadFixture(t, "testdata/webhooks/note_commit.json")

var event *CommitCommentEvent
err := json.Unmarshal(jsonObject, &event)
@@ -111,7 +111,7 @@ func TestCommitCommentEventUnmarshal(t *testing.T) {
}

func TestJobEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/job.json")
jsonObject := loadFixture(t, "testdata/webhooks/job.json")

var event *JobEvent
err := json.Unmarshal(jsonObject, &event)
@@ -195,7 +195,7 @@ func TestJobEventUnmarshal(t *testing.T) {
}

func TestDeploymentEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/deployment.json")
jsonObject := loadFixture(t, "testdata/webhooks/deployment.json")

var event *DeploymentEvent
err := json.Unmarshal(jsonObject, &event)
@@ -245,7 +245,7 @@ func TestDeploymentEventUnmarshal(t *testing.T) {
}

func TestFeatureFlagEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/feature_flag.json")
jsonObject := loadFixture(t, "testdata/webhooks/feature_flag.json")

var event *FeatureFlagEvent
err := json.Unmarshal(jsonObject, &event)
@@ -291,7 +291,7 @@ func TestFeatureFlagEventUnmarshal(t *testing.T) {
}

func TestGroupResourceAccessTokenEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/resource_access_token_group.json")
jsonObject := loadFixture(t, "testdata/webhooks/resource_access_token_group.json")
var event *GroupResourceAccessTokenEvent
err := json.Unmarshal(jsonObject, &event)
if err != nil {
@@ -326,7 +326,7 @@ func TestGroupResourceAccessTokenEventUnmarshal(t *testing.T) {
}

func TestIssueCommentEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/note_issue.json")
jsonObject := loadFixture(t, "testdata/webhooks/note_issue.json")

var event *IssueCommentEvent
err := json.Unmarshal(jsonObject, &event)
@@ -403,7 +403,7 @@ func TestIssueCommentEventUnmarshal(t *testing.T) {
}

func TestIssueEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/issue.json")
jsonObject := loadFixture(t, "testdata/webhooks/issue.json")

var event *IssueEvent
err := json.Unmarshal(jsonObject, &event)
@@ -531,7 +531,7 @@ func TestIssueEventUnmarshal(t *testing.T) {

// Generate unit test for MergeCommentEvent
func TestMergeCommentEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/note_merge_request.json")
jsonObject := loadFixture(t, "testdata/webhooks/note_merge_request.json")

var event *MergeCommentEvent
err := json.Unmarshal(jsonObject, &event)
@@ -653,7 +653,7 @@ func TestMergeCommentEventUnmarshal(t *testing.T) {
}

func TestMergeEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/merge_request.json")
jsonObject := loadFixture(t, "testdata/webhooks/merge_request.json")

var event *MergeEvent
err := json.Unmarshal(jsonObject, &event)
@@ -845,7 +845,7 @@ func TestMergeEventUnmarshal(t *testing.T) {
}

func TestMemberEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/member.json")
jsonObject := loadFixture(t, "testdata/webhooks/member.json")

var event *MemberEvent
err := json.Unmarshal(jsonObject, &event)
@@ -914,7 +914,7 @@ func TestMemberEventUnmarshal(t *testing.T) {
}

func TestMergeEventUnmarshalFromGroup(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/group_merge_request.json")
jsonObject := loadFixture(t, "testdata/webhooks/group_merge_request.json")

var event *MergeEvent
err := json.Unmarshal(jsonObject, &event)
@@ -968,7 +968,7 @@ func TestMergeEventUnmarshalFromGroup(t *testing.T) {
}

func TestPipelineEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/pipeline.json")
jsonObject := loadFixture(t, "testdata/webhooks/pipeline.json")

var event *PipelineEvent
err := json.Unmarshal(jsonObject, &event)
@@ -1062,7 +1062,7 @@ func TestPipelineEventUnmarshal(t *testing.T) {
}

func TestProjectResourceAccessTokenEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/resource_access_token_project.json")
jsonObject := loadFixture(t, "testdata/webhooks/resource_access_token_project.json")
var event *ProjectResourceAccessTokenEvent
err := json.Unmarshal(jsonObject, &event)
if err != nil {
@@ -1110,7 +1110,7 @@ func TestProjectResourceAccessTokenEventUnmarshal(t *testing.T) {
}

func TestPushEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/push.json")
jsonObject := loadFixture(t, "testdata/webhooks/push.json")
var event *PushEvent
err := json.Unmarshal(jsonObject, &event)
if err != nil {
@@ -1155,7 +1155,7 @@ func TestPushEventUnmarshal(t *testing.T) {
}

func TestReleaseEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/release.json")
jsonObject := loadFixture(t, "testdata/webhooks/release.json")

var event *ReleaseEvent
err := json.Unmarshal(jsonObject, &event)
@@ -1197,7 +1197,7 @@ func TestReleaseEventUnmarshal(t *testing.T) {
}

func TestSubGroupEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/subgroup.json")
jsonObject := loadFixture(t, "testdata/webhooks/subgroup.json")

var event *SubGroupEvent
err := json.Unmarshal(jsonObject, &event)
@@ -1227,7 +1227,7 @@ func TestSubGroupEventUnmarshal(t *testing.T) {
}

func TestTagEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/tag_push.json")
jsonObject := loadFixture(t, "testdata/webhooks/tag_push.json")
var event *TagEvent
err := json.Unmarshal(jsonObject, &event)
if err != nil {
29 changes: 29 additions & 0 deletions gitlab.go
Original file line number Diff line number Diff line change
@@ -70,6 +70,11 @@ const (
var ErrNotFound = errors.New("404 Not Found")

// A Client manages communication with the GitLab API.
//
// Deprecated: use gitlab.com/gitlab-org/api/client-go instead.
// See https://gitlab.com/gitlab-org/api/client-go
//
// This package is completely frozen, nothing will be added, removed or changed.
type Client struct {
// HTTP client used to communicate with the API.
client *retryablehttp.Client
@@ -122,6 +127,7 @@ type Client struct {
Commits *CommitsService
ContainerRegistry *ContainerRegistryService
CustomAttribute *CustomAttributesService
DependencyListExport *DependencyListExportService
DeployKeys *DeployKeysService
DeployTokens *DeployTokensService
DeploymentMergeRequests *DeploymentMergeRequestsService
@@ -195,6 +201,7 @@ type Client struct {
ProjectFeatureFlags *ProjectFeatureFlagService
ProjectImportExport *ProjectImportExportService
ProjectIterations *ProjectIterationsService
ProjectMarkdownUploads *ProjectMarkdownUploadsService
ProjectMembers *ProjectMembersService
ProjectMirrors *ProjectMirrorService
ProjectRepositoryStorageMove *ProjectRepositoryStorageMoveService
@@ -258,6 +265,11 @@ type RateLimiter interface {

// NewClient returns a new GitLab API client. To use API methods which require
// authentication, provide a valid private or personal token.
//
// Deprecated: This module has been migrated to gitlab.com/gitlab-org/api/client-go.
// See https://gitlab.com/gitlab-org/api/client-go
//
// This package is completely frozen, nothing will be added, removed or changed.
func NewClient(token string, options ...ClientOptionFunc) (*Client, error) {
client, err := newClient(options...)
if err != nil {
@@ -270,6 +282,11 @@ func NewClient(token string, options ...ClientOptionFunc) (*Client, error) {

// NewBasicAuthClient returns a new GitLab API client. To use API methods which
// require authentication, provide a valid username and password.
//
// Deprecated: This module has been migrated to gitlab.com/gitlab-org/api/client-go.
// See https://gitlab.com/gitlab-org/api/client-go
//
// This package is completely frozen, nothing will be added, removed or changed.
func NewBasicAuthClient(username, password string, options ...ClientOptionFunc) (*Client, error) {
client, err := newClient(options...)
if err != nil {
@@ -285,6 +302,11 @@ func NewBasicAuthClient(username, password string, options ...ClientOptionFunc)

// NewJobClient returns a new GitLab API client. To use API methods which require
// authentication, provide a valid job token.
//
// Deprecated: This module has been migrated to gitlab.com/gitlab-org/api/client-go.
// See https://gitlab.com/gitlab-org/api/client-go
//
// This package is completely frozen, nothing will be added, removed or changed.
func NewJobClient(token string, options ...ClientOptionFunc) (*Client, error) {
client, err := newClient(options...)
if err != nil {
@@ -297,6 +319,11 @@ func NewJobClient(token string, options ...ClientOptionFunc) (*Client, error) {

// NewOAuthClient returns a new GitLab API client. To use API methods which
// require authentication, provide a valid oauth token.
//
// Deprecated: This module has been migrated to gitlab.com/gitlab-org/api/client-go.
// See https://gitlab.com/gitlab-org/api/client-go
//
// This package is completely frozen, nothing will be added, removed or changed.
func NewOAuthClient(token string, options ...ClientOptionFunc) (*Client, error) {
client, err := newClient(options...)
if err != nil {
@@ -360,6 +387,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) {
c.Commits = &CommitsService{client: c}
c.ContainerRegistry = &ContainerRegistryService{client: c}
c.CustomAttribute = &CustomAttributesService{client: c}
c.DependencyListExport = &DependencyListExportService{client: c}
c.DeployKeys = &DeployKeysService{client: c}
c.DeployTokens = &DeployTokensService{client: c}
c.DeploymentMergeRequests = &DeploymentMergeRequestsService{client: c}
@@ -433,6 +461,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) {
c.ProjectFeatureFlags = &ProjectFeatureFlagService{client: c}
c.ProjectImportExport = &ProjectImportExportService{client: c}
c.ProjectIterations = &ProjectIterationsService{client: c}
c.ProjectMarkdownUploads = &ProjectMarkdownUploadsService{client: c}
c.ProjectMembers = &ProjectMembersService{client: c}
c.ProjectMirrors = &ProjectMirrorService{client: c}
c.ProjectRepositoryStorageMove = &ProjectRepositoryStorageMoveService{client: c}
6 changes: 3 additions & 3 deletions gitlab_test.go
Original file line number Diff line number Diff line change
@@ -22,7 +22,6 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"os"
@@ -250,10 +249,11 @@ func TestRequestWithContext(t *testing.T) {
}
}

func loadFixture(filePath string) []byte {
func loadFixture(t *testing.T, filePath string) []byte {
t.Helper()
content, err := os.ReadFile(filePath)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

return content
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Deprecated: This module has been migrated to gitlab.com/gitlab-org/api/client-go. See https://gitlab.com/gitlab-org/api/client-go
module github.com/xanzy/go-gitlab

go 1.19
7 changes: 3 additions & 4 deletions group_import_export_test.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ package gitlab
import (
"fmt"
"io"
"log"
"net/http"
"os"
"reflect"
@@ -58,14 +57,14 @@ func TestGroupImport(t *testing.T) {
tmpfile, err := os.CreateTemp(os.TempDir(), "example.*.tar.gz")
if err != nil {
tmpfile.Close()
log.Fatal(err)
t.Fatal(err)
}
if _, err := tmpfile.Write(content); err != nil {
tmpfile.Close()
log.Fatal(err)
t.Fatal(err)
}
if err := tmpfile.Close(); err != nil {
log.Fatal(err)
t.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up

15 changes: 7 additions & 8 deletions group_labels_test.go
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ package gitlab

import (
"fmt"
"log"
"net/http"
"reflect"
"testing"
@@ -38,7 +37,7 @@ func TestCreateGroupGroupLabel(t *testing.T) {
}
label, _, err := client.GroupLabels.CreateGroupLabel("1", l)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
want := &GroupLabel{ID: 1, Name: "MyGroupLabel", Color: "#11FF22"}
if !reflect.DeepEqual(want, label) {
@@ -55,7 +54,7 @@ func TestDeleteGroupLabelByID(t *testing.T) {

_, err := client.GroupLabels.DeleteGroupLabel("1", "1", nil)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

@@ -68,7 +67,7 @@ func TestDeleteGroupLabelByName(t *testing.T) {

_, err := client.GroupLabels.DeleteGroupLabel("1", "MyGroupLabel", nil)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

@@ -89,10 +88,10 @@ func TestUpdateGroupLabel(t *testing.T) {
label, resp, err := client.GroupLabels.UpdateGroupLabel("1", "MyGroupLabel", l)

if resp == nil {
log.Fatal(err)
t.Fatal(err)
}
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &GroupLabel{ID: 1, Name: "NewLabel", Color: "#11FF23", Description: "This is updated label"}
@@ -112,7 +111,7 @@ func TestSubscribeToGroupLabel(t *testing.T) {

label, _, err := client.GroupLabels.SubscribeToGroupLabel("1", "5")
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
want := &GroupLabel{ID: 5, Name: "kind/bug", Color: "#d9534f", Description: "Bug reported by user", OpenIssuesCount: 1, ClosedIssuesCount: 0, OpenMergeRequestsCount: 1, Subscribed: true}
if !reflect.DeepEqual(want, label) {
@@ -129,7 +128,7 @@ func TestUnsubscribeFromGroupLabel(t *testing.T) {

_, err := client.GroupLabels.UnsubscribeFromGroupLabel("1", "5")
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

2 changes: 2 additions & 0 deletions group_variables.go
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@ type GroupVariable struct {
VariableType VariableTypeValue `json:"variable_type"`
Protected bool `json:"protected"`
Masked bool `json:"masked"`
Hidden bool `json:"hidden"`
Raw bool `json:"raw"`
EnvironmentScope string `json:"environment_scope"`
Description string `json:"description"`
@@ -127,6 +128,7 @@ type CreateGroupVariableOptions struct {
Description *string `url:"description,omitempty" json:"description,omitempty"`
EnvironmentScope *string `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
Masked *bool `url:"masked,omitempty" json:"masked,omitempty"`
MaskedAndHidden *bool `url:"masked_and_hidden,omitempty" json:"hidden,omitempty"`
Protected *bool `url:"protected,omitempty" json:"protected,omitempty"`
Raw *bool `url:"raw,omitempty" json:"raw,omitempty"`
VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
72 changes: 61 additions & 11 deletions group_variables_test.go
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ func TestListGroupVariabless(t *testing.T) {
mux.HandleFunc("/api/v4/groups/1/variables",
func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, `[{"key": "TEST_VARIABLE_1","value": "test1","protected": false,"masked": true}]`)
fmt.Fprint(w, `[{"key": "TEST_VARIABLE_1","value": "test1","protected": false,"masked": true,"hidden": true}]`)
})

variables, _, err := client.GroupVariables.ListVariables(1, &ListGroupVariablesOptions{})
@@ -43,6 +43,7 @@ func TestListGroupVariabless(t *testing.T) {
Value: "test1",
Protected: false,
Masked: true,
Hidden: true,
},
}

@@ -58,15 +59,15 @@ func TestGetGroupVariable(t *testing.T) {
func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
testParams(t, r, "filter%5Benvironment_scope%5D=prod")
fmt.Fprint(w, `{"key": "TEST_VARIABLE_1","value": "test1","protected": false,"masked": true}`)
fmt.Fprint(w, `{"key": "TEST_VARIABLE_1","value": "test1","protected": false,"masked": true,"hidden": false}`)
})

variable, _, err := client.GroupVariables.GetVariable(1, "TEST_VARIABLE_1", &GetGroupVariableOptions{Filter: &VariableFilter{EnvironmentScope: "prod"}})
if err != nil {
t.Errorf("GroupVariables.GetVariable returned error: %v", err)
}

want := &GroupVariable{Key: "TEST_VARIABLE_1", Value: "test1", Protected: false, Masked: true}
want := &GroupVariable{Key: "TEST_VARIABLE_1", Value: "test1", Protected: false, Masked: true, Hidden: false}
if !reflect.DeepEqual(want, variable) {
t.Errorf("GroupVariables.GetVariable returned %+v, want %+v", variable, want)
}
@@ -78,22 +79,51 @@ func TestCreateGroupVariable(t *testing.T) {
mux.HandleFunc("/api/v4/groups/1/variables",
func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPost)
fmt.Fprint(w, `{"key": "TEST_VARIABLE_1","value": "test1","protected": false,"masked": true}`)
fmt.Fprint(w, `{"key": "TEST_VARIABLE_1","value":"test1","protected": false,"masked": true,"hidden": false}`)
})

opt := &CreateGroupVariableOptions{
Key: Ptr("TEST_VARIABLE_1"),
Value: Ptr("test1"),
Protected: Ptr(false),
Masked: Ptr(true),
Key: Ptr("TEST_VARIABLE_1"),
Value: Ptr("test1"),
Protected: Ptr(false),
Masked: Ptr(true),
MaskedAndHidden: Ptr(false),
}

variable, _, err := client.GroupVariables.CreateVariable(1, opt, nil)
if err != nil {
t.Errorf("GroupVariables.CreateVariable returned error: %v", err)
}

want := &GroupVariable{Key: "TEST_VARIABLE_1", Value: "test1", Protected: false, Masked: true}
want := &GroupVariable{Key: "TEST_VARIABLE_1", Value: "test1", Protected: false, Masked: true, Hidden: false}
if !reflect.DeepEqual(want, variable) {
t.Errorf("GroupVariables.CreateVariable returned %+v, want %+v", variable, want)
}
}

func TestCreateGroupVariable_MaskedAndHidden(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/groups/1/variables",
func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPost)
fmt.Fprint(w, `{"key": "TEST_VARIABLE_1","protected": false,"masked": true,"hidden": true}`)
})

opt := &CreateGroupVariableOptions{
Key: Ptr("TEST_VARIABLE_1"),
Value: Ptr("test1"),
Protected: Ptr(false),
Masked: Ptr(true),
MaskedAndHidden: Ptr(true),
}

variable, _, err := client.GroupVariables.CreateVariable(1, opt, nil)
if err != nil {
t.Errorf("GroupVariables.CreateVariable returned error: %v", err)
}

want := &GroupVariable{Key: "TEST_VARIABLE_1", Protected: false, Masked: true, Hidden: true}
if !reflect.DeepEqual(want, variable) {
t.Errorf("GroupVariables.CreateVariable returned %+v, want %+v", variable, want)
}
@@ -126,15 +156,35 @@ func TestUpdateGroupVariable(t *testing.T) {
mux.HandleFunc("/api/v4/groups/1/variables/TEST_VARIABLE_1",
func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPut)
fmt.Fprint(w, `{"key": "TEST_VARIABLE_1","value": "test1","protected": false,"masked": true}`)
fmt.Fprint(w, `{"key": "TEST_VARIABLE_1","value": "test1","protected": false,"masked": true,"hidden": false}`)
})

variable, _, err := client.GroupVariables.UpdateVariable(1, "TEST_VARIABLE_1", &UpdateGroupVariableOptions{})
if err != nil {
t.Errorf("GroupVariables.UpdateVariable returned error: %v", err)
}

want := &GroupVariable{Key: "TEST_VARIABLE_1", Value: "test1", Protected: false, Masked: true, Hidden: false}
if !reflect.DeepEqual(want, variable) {
t.Errorf("Groups.UpdatedGroup returned %+v, want %+v", variable, want)
}
}

func TestUpdateGroupVariable_MaskedAndHidden(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/groups/1/variables/TEST_VARIABLE_1",
func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPut)
fmt.Fprint(w, `{"key": "TEST_VARIABLE_1","protected": false,"masked": true,"hidden": true}`)
})

variable, _, err := client.GroupVariables.UpdateVariable(1, "TEST_VARIABLE_1", &UpdateGroupVariableOptions{})
if err != nil {
t.Errorf("GroupVariables.UpdateVariable returned error: %v", err)
}

want := &GroupVariable{Key: "TEST_VARIABLE_1", Value: "test1", Protected: false, Masked: true}
want := &GroupVariable{Key: "TEST_VARIABLE_1", Protected: false, Masked: true, Hidden: true}
if !reflect.DeepEqual(want, variable) {
t.Errorf("Groups.UpdatedGroup returned %+v, want %+v", variable, want)
}
7 changes: 3 additions & 4 deletions issues_statistics_test.go
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ package gitlab

import (
"fmt"
"log"
"net/http"
"reflect"
"testing"
@@ -40,7 +39,7 @@ func TestGetIssuesStatistics(t *testing.T) {

issue, _, err := client.IssuesStatistics.GetIssuesStatistics(opt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &IssuesStatistics{
@@ -82,7 +81,7 @@ func TestGetGroupIssuesStatistics(t *testing.T) {

issue, _, err := client.IssuesStatistics.GetGroupIssuesStatistics(1, opt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &IssuesStatistics{
@@ -124,7 +123,7 @@ func TestGetProjectIssuesStatistics(t *testing.T) {

issue, _, err := client.IssuesStatistics.GetProjectIssuesStatistics(1, opt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &IssuesStatistics{
59 changes: 29 additions & 30 deletions issues_test.go
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ package gitlab

import (
"fmt"
"log"
"net/http"
"reflect"
"testing"
@@ -36,7 +35,7 @@ func TestGetIssue(t *testing.T) {

issue, _, err := client.Issues.GetIssue("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -62,7 +61,7 @@ func TestGetIssueByID(t *testing.T) {

issue, _, err := client.Issues.GetIssueByID(5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -88,7 +87,7 @@ func TestDeleteIssue(t *testing.T) {

_, err := client.Issues.DeleteIssue("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

@@ -105,7 +104,7 @@ func TestReorderIssue(t *testing.T) {

issue, _, err := client.Issues.ReorderIssue("1", 5, &opt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -131,7 +130,7 @@ func TestMoveIssue(t *testing.T) {

issue, _, err := client.Issues.MoveIssue("1", 11, &MoveIssueOptions{ToProjectID: Ptr(5)})
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -157,7 +156,7 @@ func TestMoveIssue(t *testing.T) {
})
movedIssue, _, err := client.Issues.GetIssue("1", 11)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

wantedMovedIssue := &Issue{
@@ -208,7 +207,7 @@ func TestListIssues(t *testing.T) {

issues, _, err := client.Issues.ListIssues(listProjectIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -274,7 +273,7 @@ func TestListIssuesWithLabelDetails(t *testing.T) {

issues, _, err := client.Issues.ListIssues(listProjectIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -318,7 +317,7 @@ func TestListIssuesSearchInTitle(t *testing.T) {

issues, _, err := client.Issues.ListIssues(listProjectIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -356,7 +355,7 @@ func TestListIssuesSearchInDescription(t *testing.T) {

issues, _, err := client.Issues.ListIssues(listProjectIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -401,7 +400,7 @@ func TestListIssuesSearchByIterationID(t *testing.T) {

issues, _, err := client.Issues.ListIssues(listProjectIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -438,7 +437,7 @@ func TestListProjectIssues(t *testing.T) {
}
issues, _, err := client.Issues.ListProjectIssues("1", listProjectIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -484,7 +483,7 @@ func TestListProjectIssuesSearchByIterationID(t *testing.T) {

issues, _, err := client.Issues.ListProjectIssues(1, listProjectIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -523,7 +522,7 @@ func TestListGroupIssues(t *testing.T) {

issues, _, err := client.Issues.ListGroupIssues("1", listGroupIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -569,7 +568,7 @@ func TestListGroupIssuesSearchByIterationID(t *testing.T) {

issues, _, err := client.Issues.ListGroupIssues(1, listProjectIssue)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*Issue{{
@@ -606,7 +605,7 @@ func TestCreateIssue(t *testing.T) {

issue, _, err := client.Issues.CreateIssue("1", createIssueOptions)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -636,7 +635,7 @@ func TestUpdateIssue(t *testing.T) {
}
issue, _, err := client.Issues.UpdateIssue(1, 5, updateIssueOpt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -662,7 +661,7 @@ func TestSubscribeToIssue(t *testing.T) {

issue, _, err := client.Issues.SubscribeToIssue("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -688,7 +687,7 @@ func TestUnsubscribeFromIssue(t *testing.T) {

issue, _, err := client.Issues.UnsubscribeFromIssue("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -720,7 +719,7 @@ func TestListMergeRequestsClosingIssue(t *testing.T) {
}
mergeRequest, _, err := client.Issues.ListMergeRequestsClosingIssue("1", 5, listMergeRequestsClosingIssueOpt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*MergeRequest{{ID: 1, Title: "test merge one"}, {ID: 2, Title: "test merge two"}}
@@ -746,7 +745,7 @@ func TestListMergeRequestsRelatedToIssue(t *testing.T) {
}
mergeRequest, _, err := client.Issues.ListMergeRequestsRelatedToIssue("1", 5, listMergeRequestsRelatedToIssueOpt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*MergeRequest{{ID: 1, Title: "test merge one"}, {ID: 2, Title: "test merge two"}}
@@ -770,7 +769,7 @@ func TestSetTimeEstimate(t *testing.T) {

timeState, _, err := client.Issues.SetTimeEstimate("1", 5, setTimeEstiOpt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
want := &TimeStats{HumanTimeEstimate: "3h 30m", HumanTotalTimeSpent: "", TimeEstimate: 12600, TotalTimeSpent: 0}

@@ -789,7 +788,7 @@ func TestResetTimeEstimate(t *testing.T) {

timeState, _, err := client.Issues.ResetTimeEstimate("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
want := &TimeStats{HumanTimeEstimate: "", HumanTotalTimeSpent: "", TimeEstimate: 0, TotalTimeSpent: 0}

@@ -814,7 +813,7 @@ func TestAddSpentTime(t *testing.T) {

timeState, _, err := client.Issues.AddSpentTime("1", 5, addSpentTimeOpt)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
want := &TimeStats{HumanTimeEstimate: "", HumanTotalTimeSpent: "1h", TimeEstimate: 0, TotalTimeSpent: 3600}

@@ -834,7 +833,7 @@ func TestResetSpentTime(t *testing.T) {

timeState, _, err := client.Issues.ResetSpentTime("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &TimeStats{HumanTimeEstimate: "", HumanTotalTimeSpent: "", TimeEstimate: 0, TotalTimeSpent: 0}
@@ -854,7 +853,7 @@ func TestGetTimeSpent(t *testing.T) {

timeState, _, err := client.Issues.GetTimeSpent("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &TimeStats{HumanTimeEstimate: "2h", HumanTotalTimeSpent: "1h", TimeEstimate: 7200, TotalTimeSpent: 3600}
@@ -876,7 +875,7 @@ func TestGetIssueParticipants(t *testing.T) {

issueParticipants, _, err := client.Issues.GetParticipants("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*BasicUser{
@@ -900,7 +899,7 @@ func TestGetIssueMilestone(t *testing.T) {

issue, _, err := client.Issues.GetIssue("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
@@ -936,7 +935,7 @@ func TestGetIssueGroupMilestone(t *testing.T) {

issue, _, err := client.Issues.GetIssue("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Issue{
15 changes: 7 additions & 8 deletions labels_test.go
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ package gitlab

import (
"fmt"
"log"
"net/http"
"reflect"
"testing"
@@ -40,7 +39,7 @@ func TestCreateLabel(t *testing.T) {
}
label, _, err := client.Labels.CreateLabel("1", l)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
want := &Label{ID: 1, Name: "MyLabel", Color: "#11FF22", Priority: 2}
if !reflect.DeepEqual(want, label) {
@@ -58,7 +57,7 @@ func TestDeleteLabelbyID(t *testing.T) {
// Delete label
_, err := client.Labels.DeleteLabel("1", "1", nil)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

@@ -76,7 +75,7 @@ func TestDeleteLabelbyName(t *testing.T) {

_, err := client.Labels.DeleteLabel("1", "MyLabel", label)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

@@ -99,10 +98,10 @@ func TestUpdateLabel(t *testing.T) {
label, resp, err := client.Labels.UpdateLabel("1", "MyLabel", l)

if resp == nil {
log.Fatal(err)
t.Fatal(err)
}
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := &Label{ID: 1, Name: "New Label", Color: "#11FF23", Description: "This is updated label", Priority: 42}
@@ -122,7 +121,7 @@ func TestSubscribeToLabel(t *testing.T) {

label, _, err := client.Labels.SubscribeToLabel("1", "5")
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
want := &Label{ID: 5, Name: "kind/bug", Color: "#d9534f", Description: "Bug reported by user", OpenIssuesCount: 1, ClosedIssuesCount: 0, OpenMergeRequestsCount: 1, Subscribed: true}
if !reflect.DeepEqual(want, label) {
@@ -139,7 +138,7 @@ func TestUnsubscribeFromLabel(t *testing.T) {

_, err := client.Labels.UnsubscribeFromLabel("1", "5")
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
}

5 changes: 2 additions & 3 deletions merge_requests_test.go
Original file line number Diff line number Diff line change
@@ -19,7 +19,6 @@ package gitlab
import (
"encoding/json"
"fmt"
"log"
"net/http"
"reflect"
"testing"
@@ -346,7 +345,7 @@ func TestGetMergeRequestParticipants(t *testing.T) {

mergeRequestParticipants, _, err := client.MergeRequests.GetMergeRequestParticipants("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

want := []*BasicUser{
@@ -371,7 +370,7 @@ func TestGetMergeRequestReviewers(t *testing.T) {

mergeRequestReviewers, _, err := client.MergeRequests.GetMergeRequestReviewers("1", 5)
if err != nil {
log.Fatal(err)
t.Fatal(err)
}

createdAt := time.Date(2022, 0o7, 27, 17, 3, 27, 684000000, time.UTC)
210 changes: 210 additions & 0 deletions project_markdown_uploads.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
//
// Copyright 2024, Sander van Harmelen
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
"bytes"
"fmt"
"io"
"net/http"
"time"
)

// ProjectMarkdownUploadsService handles communication with the project markdown uploads
// related methods of the GitLab API.
//
// Gitlab API docs: https://docs.gitlab.com/ee/api/project_markdown_uploads.html
type ProjectMarkdownUploadsService struct {
client *Client
}

// ProjectMarkdownUploadedFile represents a single project markdown uploaded file.
//
// Gitlab API docs: https://docs.gitlab.com/ee/api/project_markdown_uploads.html
type ProjectMarkdownUploadedFile struct {
ID int `json:"id"`
Alt string `json:"alt"`
URL string `json:"url"`
FullPath string `json:"full_path"`
Markdown string `json:"markdown"`
}

// ProjectMarkdownUpload represents a single project markdown upload.
//
// Gitlab API docs: https://docs.gitlab.com/ee/api/project_markdown_uploads.html
type ProjectMarkdownUpload struct {
ID int `json:"id"`
Size int `json:"size"`
Filename string `json:"filename"`
CreatedAt *time.Time `json:"created_at"`
UploadedBy *User `json:"uploaded_by"`
}

// Gets a string representation of a ProjectMarkdownUpload.
//
// GitLab API docs: https://docs.gitlab.com/ee/api/project_markdown_uploads.html
func (m ProjectMarkdownUpload) String() string {
return Stringify(m)
}

// UploadProjectMarkdown uploads a markdown file to a project.
//
// GitLab docs:
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#upload-a-file
func (s *ProjectMarkdownUploadsService) UploadProjectMarkdown(pid interface{}, content io.Reader, options ...RequestOptionFunc) (*ProjectMarkdownUploadedFile, *Response, error) {
project, err := parseID(pid)
if err != nil {
return nil, nil, err
}
u := fmt.Sprintf("projects/%s/uploads", PathEscape(project))

// We need to create the request as a GET request to make sure the options
// are set correctly. After the request is created we will overwrite both
// the method and the body.
req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
if err != nil {
return nil, nil, err
}

// Overwrite the method and body.
req.Method = http.MethodPost
req.SetBody(content)

f := new(ProjectMarkdownUploadedFile)
resp, err := s.client.Do(req, f)
if err != nil {
return nil, resp, err
}

return f, resp, nil
}

// ListProjectMarkdownUploads gets all markdown uploads for a project.
//
// GitLab API Docs:
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#list-uploads
func (s *ProjectMarkdownUploadsService) ListProjectMarkdownUploads(pid interface{}, options ...RequestOptionFunc) ([]*ProjectMarkdownUpload, *Response, error) {
project, err := parseID(pid)
if err != nil {
return nil, nil, err
}
u := fmt.Sprintf("projects/%s/uploads", PathEscape(project))

req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
if err != nil {
return nil, nil, err
}

var uploads []*ProjectMarkdownUpload
resp, err := s.client.Do(req, &uploads)
if err != nil {
return nil, resp, err
}

return uploads, resp, err
}

// DownloadProjectMarkdownUploadByID downloads a specific upload by ID.
//
// GitLab API Docs:
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#download-an-uploaded-file-by-id
func (s *ProjectMarkdownUploadsService) DownloadProjectMarkdownUploadByID(pid interface{}, uploadID int, options ...RequestOptionFunc) ([]byte, *Response, error) {
project, err := parseID(pid)
if err != nil {
return nil, nil, err
}
u := fmt.Sprintf("projects/%s/uploads/%d", PathEscape(project), uploadID)

req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
if err != nil {
return nil, nil, err
}

var f bytes.Buffer
resp, err := s.client.Do(req, &f)
if err != nil {
return nil, resp, err
}

return f.Bytes(), resp, err
}

// DownloadProjectMarkdownUploadBySecretAndFilename downloads a specific upload
// by secret and filename.
//
// GitLab API Docs:
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#download-an-uploaded-file-by-secret-and-filename
func (s *ProjectMarkdownUploadsService) DownloadProjectMarkdownUploadBySecretAndFilename(pid interface{}, secret string, filename string, options ...RequestOptionFunc) ([]byte, *Response, error) {
project, err := parseID(pid)
if err != nil {
return nil, nil, err
}
u := fmt.Sprintf("projects/%s/uploads/%s/%s", PathEscape(project), PathEscape(secret), PathEscape(filename))

req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
if err != nil {
return nil, nil, err
}

var f bytes.Buffer
resp, err := s.client.Do(req, &f)
if err != nil {
return nil, resp, err
}

return f.Bytes(), resp, err
}

// DeleteProjectMarkdownUploadByID deletes an upload by ID.
//
// GitLab API Docs:
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#delete-an-uploaded-file-by-id
func (s *ProjectMarkdownUploadsService) DeleteProjectMarkdownUploadByID(pid interface{}, uploadID int, options ...RequestOptionFunc) (*Response, error) {
project, err := parseID(pid)
if err != nil {
return nil, err
}
u := fmt.Sprintf("projects/%s/uploads/%d", PathEscape(project), uploadID)

req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
if err != nil {
return nil, err
}

return s.client.Do(req, nil)
}

// DeleteProjectMarkdownUploadBySecretAndFilename deletes an upload
// by secret and filename.
//
// GitLab API Docs:
// https://docs.gitlab.com/ee/api/project_markdown_uploads.html#delete-an-uploaded-file-by-secret-and-filename
func (s *ProjectMarkdownUploadsService) DeleteProjectMarkdownUploadBySecretAndFilename(pid interface{}, secret string, filename string, options ...RequestOptionFunc) (*Response, error) {
project, err := parseID(pid)
if err != nil {
return nil, err
}
u := fmt.Sprintf("projects/%s/uploads/%s/%s",
PathEscape(project), PathEscape(secret), PathEscape(filename))

req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
if err != nil {
return nil, err
}

return s.client.Do(req, nil)
}
163 changes: 163 additions & 0 deletions project_markdown_uploads_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package gitlab

import (
"fmt"
"net/http"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestProjectMarkdownUploads_UploadProjectMarkdown(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/projects/1/uploads", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPost)
fmt.Fprint(w, `
{
"id": 5,
"alt": "dk",
"url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
"full_path": "/-/project/1234/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
"markdown": "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"
}
`)
})

want := &ProjectMarkdownUploadedFile{
ID: 5,
Alt: "dk",
URL: "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
FullPath: "/-/project/1234/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
Markdown: "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)",
}

content := strings.NewReader("bar = baz")
upload, resp, err := client.ProjectMarkdownUploads.UploadProjectMarkdown(1, content)
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, want, upload)
}

func TestProjectMarkdownUploads_ListProjectMarkdownUploads(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/projects/1/uploads", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, `
[
{
"id": 1,
"size": 1024,
"filename": "image.png",
"created_at":"2024-06-20T15:53:03.000Z",
"uploaded_by": {
"id": 18,
"name" : "Alexandra Bashirian",
"username" : "eileen.lowe"
}
},
{
"id": 2,
"size": 512,
"filename": "other-image.png",
"created_at":"2024-06-19T15:53:03.000Z",
"uploaded_by": null
}
]
`)
})

created1 := time.Date(2024, 6, 20, 15, 53, 3, 0, time.UTC)
created2 := time.Date(2024, 6, 19, 15, 53, 3, 0, time.UTC)
want := []*ProjectMarkdownUpload{
{
ID: 1,
Size: 1024,
Filename: "image.png",
CreatedAt: &created1,
UploadedBy: &User{
ID: 18,
Name: "Alexandra Bashirian",
Username: "eileen.lowe",
},
},
{
ID: 2,
Size: 512,
Filename: "other-image.png",
CreatedAt: &created2,
},
}

uploads, resp, err := client.ProjectMarkdownUploads.ListProjectMarkdownUploads(1)
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, want, uploads)
}

func TestProjectMarkdownUploads_DownloadProjectMarkdownUploadByID(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/projects/1/uploads/2", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, strings.TrimSpace(`
bar = baz
`))
})

want := []byte("bar = baz")

bytes, resp, err := client.ProjectMarkdownUploads.DownloadProjectMarkdownUploadByID(1, 2)
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, want, bytes)
}

func TestProjectMarkdownUploads_DownloadProjectMarkdownUploadBySecretAndFilename(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/projects/1/uploads/secret/filename", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, strings.TrimSpace(`
bar = baz
`))
})

want := []byte("bar = baz")

bytes, resp, err := client.ProjectMarkdownUploads.DownloadProjectMarkdownUploadBySecretAndFilename(1, "secret", "filename")
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, want, bytes)
}

func TestProjectMarkdownUploads_DeleteProjectMarkdownUploadByID(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/projects/1/uploads/2", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
w.WriteHeader(204)
})

resp, err := client.ProjectMarkdownUploads.DeleteProjectMarkdownUploadByID(1, 2)
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, 204, resp.StatusCode)
}

func TestProjectMarkdownUploads_DeleteProjectMarkdownUploadBySecretAndFilename(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/projects/1/uploads/secret/filename", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
w.WriteHeader(204)
})

resp, err := client.ProjectMarkdownUploads.DeleteProjectMarkdownUploadBySecretAndFilename(1, "secret", "filename")
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, 204, resp.StatusCode)
}
2 changes: 2 additions & 0 deletions project_variables.go
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@ type ProjectVariable struct {
VariableType VariableTypeValue `json:"variable_type"`
Protected bool `json:"protected"`
Masked bool `json:"masked"`
Hidden bool `json:"hidden"`
Raw bool `json:"raw"`
EnvironmentScope string `json:"environment_scope"`
Description string `json:"description"`
@@ -132,6 +133,7 @@ type CreateProjectVariableOptions struct {
Description *string `url:"description,omitempty" json:"description,omitempty"`
EnvironmentScope *string `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
Masked *bool `url:"masked,omitempty" json:"masked,omitempty"`
MaskedAndHidden *bool `url:"masked_and_hidden,omitempty" json:"masked_and_hidden,omitempty"`
Protected *bool `url:"protected,omitempty" json:"protected,omitempty"`
Raw *bool `url:"raw,omitempty" json:"raw,omitempty"`
VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
110 changes: 110 additions & 0 deletions project_variables_test.go
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ func TestProjectVariablesService_ListVariables(t *testing.T) {
VariableType: "env_var",
Protected: false,
Masked: false,
Hidden: false,
EnvironmentScope: "",
Description: "test variable 1",
}}
@@ -69,6 +70,7 @@ func TestProjectVariablesService_GetVariable(t *testing.T) {
"value": "TEST_1",
"protected": false,
"masked": true,
"hidden": true,
"description": "test variable 1"
}
`)
@@ -80,6 +82,7 @@ func TestProjectVariablesService_GetVariable(t *testing.T) {
VariableType: "env_var",
Protected: false,
Masked: true,
Hidden: true,
EnvironmentScope: "",
Description: "test variable 1",
}
@@ -118,6 +121,7 @@ func TestProjectVariablesService_CreateVariable(t *testing.T) {
"protected": false,
"variable_type": "env_var",
"masked": false,
"masked_and_hidden": false,
"environment_scope": "*",
"description": "new variable"
}
@@ -130,6 +134,57 @@ func TestProjectVariablesService_CreateVariable(t *testing.T) {
VariableType: "env_var",
Protected: false,
Masked: false,
Hidden: false,
EnvironmentScope: "*",
Description: "new variable",
}

pv, resp, err := client.ProjectVariables.CreateVariable(1, &CreateProjectVariableOptions{Description: Ptr("new variable")}, nil)
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, want, pv)

pv, resp, err = client.ProjectVariables.CreateVariable(1.01, nil, nil)
require.EqualError(t, err, "invalid ID type 1.01, the ID must be an int or a string")
require.Nil(t, resp)
require.Nil(t, pv)

pv, resp, err = client.ProjectVariables.CreateVariable(1, nil, nil, errorOption)
require.EqualError(t, err, "RequestOptionFunc returns an error")
require.Nil(t, resp)
require.Nil(t, pv)

pv, resp, err = client.ProjectVariables.CreateVariable(2, nil, nil)
require.Error(t, err)
require.Nil(t, pv)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestProjectVariablesService_CreateVariable_MaskedAndHidden(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/projects/1/variables", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPost)
testBody(t, r, `{"description":"new variable"}`)
fmt.Fprintf(w, `
{
"key": "NEW_VARIABLE",
"protected": false,
"variable_type": "env_var",
"masked": true,
"hidden": true,
"environment_scope": "*",
"description": "new variable"
}
`)
})

want := &ProjectVariable{
Key: "NEW_VARIABLE",
VariableType: "env_var",
Protected: false,
Masked: true,
Hidden: true,
EnvironmentScope: "*",
Description: "new variable",
}
@@ -180,6 +235,61 @@ func TestProjectVariablesService_UpdateVariable(t *testing.T) {
VariableType: "env_var",
Protected: false,
Masked: false,
Hidden: false,
EnvironmentScope: "*",
Description: "updated description",
}

pv, resp, err := client.ProjectVariables.UpdateVariable(1, "NEW_VARIABLE", &UpdateProjectVariableOptions{
Filter: &VariableFilter{EnvironmentScope: "prod"},
Description: Ptr("updated description"),
}, nil)
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, want, pv)

pv, resp, err = client.ProjectVariables.UpdateVariable(1.01, "NEW_VARIABLE", nil, nil)
require.EqualError(t, err, "invalid ID type 1.01, the ID must be an int or a string")
require.Nil(t, resp)
require.Nil(t, pv)

pv, resp, err = client.ProjectVariables.UpdateVariable(1, "NEW_VARIABLE", nil, nil, errorOption)
require.EqualError(t, err, "RequestOptionFunc returns an error")
require.Nil(t, resp)
require.Nil(t, pv)

pv, resp, err = client.ProjectVariables.UpdateVariable(2, "NEW_VARIABLE", nil, nil)
require.Error(t, err)
require.Nil(t, pv)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestProjectVariablesService_UpdateVariable_MaskedAndHidden(t *testing.T) {
mux, client := setup(t)

mux.HandleFunc("/api/v4/projects/1/variables/NEW_VARIABLE", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPut)
testBody(t, r, `{"description":"updated description","filter":{"environment_scope":"prod"}}`)
fmt.Fprintf(w, `
{
"key": "NEW_VARIABLE",
"value": null,
"protected": false,
"variable_type": "env_var",
"masked": true,
"hidden": true,
"environment_scope": "*",
"description": "updated description"
}
`)
})

want := &ProjectVariable{
Key: "NEW_VARIABLE",
VariableType: "env_var",
Protected: false,
Masked: true,
Hidden: true,
EnvironmentScope: "*",
Description: "updated description",
}
6 changes: 6 additions & 0 deletions projects.go
Original file line number Diff line number Diff line change
@@ -1270,6 +1270,8 @@ type HookCustomHeader struct {
type ProjectHook struct {
ID int `json:"id"`
URL string `json:"url"`
Name string `json:"name"`
Description string `json:"description"`
ConfidentialNoteEvents bool `json:"confidential_note_events"`
ProjectID int `json:"project_id"`
PushEvents bool `json:"push_events"`
@@ -1352,6 +1354,8 @@ func (s *ProjectsService) GetProjectHook(pid interface{}, hook int, options ...R
// GitLab API docs:
// https://docs.gitlab.com/ee/api/projects.html#add-project-hook
type AddProjectHookOptions struct {
Name *string `url:"name,omitempty" json:"name,omitempty"`
Description *string `url:"description,omitempty" json:"description,omitempty"`
ConfidentialIssuesEvents *bool `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
ConfidentialNoteEvents *bool `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
DeploymentEvents *bool `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
@@ -1403,6 +1407,8 @@ func (s *ProjectsService) AddProjectHook(pid interface{}, opt *AddProjectHookOpt
// GitLab API docs:
// https://docs.gitlab.com/ee/api/projects.html#edit-project-hook
type EditProjectHookOptions struct {
Name *string `url:"name,omitempty" json:"name,omitempty"`
Description *string `url:"description,omitempty" json:"description,omitempty"`
ConfidentialIssuesEvents *bool `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
ConfidentialNoteEvents *bool `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
DeploymentEvents *bool `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
4 changes: 4 additions & 0 deletions projects_test.go
Original file line number Diff line number Diff line change
@@ -1505,6 +1505,8 @@ func TestListProjectHooks(t *testing.T) {
{
"id": 1,
"url": "http://example.com/hook",
"name": "This is the name of an example hook",
"description": "This is the description of an example hook",
"confidential_note_events": true,
"project_id": 1,
"push_events": true,
@@ -1541,6 +1543,8 @@ func TestListProjectHooks(t *testing.T) {
want := []*ProjectHook{{
ID: 1,
URL: "http://example.com/hook",
Name: "This is the name of an example hook",
Description: "This is the description of an example hook",
ConfidentialNoteEvents: true,
ProjectID: 1,
PushEvents: true,
6 changes: 6 additions & 0 deletions testdata/create_dependency_list_export.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": 5678,
"has_finished": false,
"self": "http://gitlab.example.com/api/v4/dependency_list_exports/5678",
"download": "http://gitlab.example.com/api/v4/dependency_list_exports/5678/download"
}
6 changes: 3 additions & 3 deletions testdata/create_service_account_user.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"id": 999,
"username": "service_account_94e556c44d40d5a710ca59e3a0f40a3d",
"name": "Service account user",
"username": "serviceaccount",
"name": "Test Service Account",
"state": "active",
"locked": false,
"avatar_url": "http://localhost:3000/uploads/user/avatar/999/cd8.jpeg",
"web_url": "http://localhost:3000/service_account_94e556c44d40d5a710ca59e3a0f40a3d"
"web_url": "http://localhost:3000/serviceaccount"
}
31 changes: 31 additions & 0 deletions testdata/download_dependency_list_export.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3fa3b1c2-7e21-4dae-917b-b320f6d25ae1",
"version": 1,
"metadata": {
"timestamp": "2024-11-14T23:39:16.117Z",
"authors": [{ "name": "GitLab", "email": "support@gitlab.com" }],
"properties": [
{
"name": "gitlab:dependency_scanning:input_file:path",
"value": "my_package_manager.lock"
},
{
"name": "gitlab:dependency_scanning:package_manager:name",
"value": "my_package_manager"
},
{ "name": "gitlab:meta:schema_version", "value": "1" }
],
"tools": [{ "vendor": "GitLab", "name": "Gemnasium", "version": "5.8.0" }]
},
"components": [
{
"name": "dummy",
"version": "1.0.0",
"purl": "pkg:testing/dummy@1.0.0",
"type": "library",
"licenses": [{ "license": { "name": "unknown" } }]
}
]
}
6 changes: 6 additions & 0 deletions testdata/get_dependency_list_export.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": 5678,
"has_finished": true,
"self": "http://gitlab.example.com/api/v4/dependency_list_exports/5678",
"download": "http://gitlab.example.com/api/v4/dependency_list_exports/5678/download"
}
13 changes: 11 additions & 2 deletions users.go
Original file line number Diff line number Diff line change
@@ -1553,12 +1553,21 @@ func (s *UsersService) CreateUserRunner(opts *CreateUserRunnerOptions, options .
return r, resp, nil
}


// CreateServiceAccountUserOptions represents the available CreateServiceAccountUser() options.
//
// GitLab API docs: https://docs.gitlab.com/ee/api/user_service_accounts.html#create-a-service-account-user
type CreateServiceAccountUserOptions struct {
Name *string `url:"name,omitempty" json:"name,omitempty"`
Username *string `url:"username,omitempty" json:"username,omitempty"`
}

// CreateServiceAccountUser creates a new service account user.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/users.html#create-service-account-user
func (s *UsersService) CreateServiceAccountUser(options ...RequestOptionFunc) (*User, *Response, error) {
req, err := s.client.NewRequest(http.MethodPost, "service_accounts", nil, options)
func (s *UsersService) CreateServiceAccountUser(opts *CreateServiceAccountUserOptions, options ...RequestOptionFunc) (*User, *Response, error) {
req, err := s.client.NewRequest(http.MethodPost, "service_accounts", opts, options)
if err != nil {
return nil, nil, err
}
17 changes: 13 additions & 4 deletions users_test.go
Original file line number Diff line number Diff line change
@@ -727,20 +727,29 @@ func TestCreateServiceAccountUser(t *testing.T) {

mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodPost)
if !strings.Contains(r.Header.Get("Content-Type"), "application/json") {
t.Fatalf("Users.CreateServiceAccountUser request content-type %+v want application/json;", r.Header.Get("Content-Type"))
}
if r.ContentLength == -1 {
t.Fatalf("Users.CreateServiceAccountUser request content-length is -1")
}
mustWriteHTTPResponse(t, w, "testdata/create_service_account_user.json")
})

user, _, err := client.Users.CreateServiceAccountUser()
user, _, err := client.Users.CreateServiceAccountUser(&CreateServiceAccountUserOptions{
Name: Ptr("Test Service Account"),
Username: Ptr("serviceaccount"),
})
require.NoError(t, err)

want := &User{
ID: 999,
Username: "service_account_94e556c44d40d5a710ca59e3a0f40a3d",
Name: "Service account user",
Username: "serviceaccount",
Name: "Test Service Account",
State: "active",
Locked: false,
AvatarURL: "http://localhost:3000/uploads/user/avatar/999/cd8.jpeg",
WebURL: "http://localhost:3000/service_account_94e556c44d40d5a710ca59e3a0f40a3d",
WebURL: "http://localhost:3000/serviceaccount",
}
require.Equal(t, want, user)
}