diff --git a/go.mod b/go.mod index 3e1c5c7..9349fca 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,16 @@ go 1.24.0 require ( github.com/disintegration/imaging v1.6.2 + github.com/oapi-codegen/runtime v1.1.2 + github.com/stretchr/testify v1.11.1 github.com/tyler-smith/go-bip39 v1.1.0 golang.org/x/crypto v0.45.0 ) -require golang.org/x/image v0.18.0 // indirect +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/image v0.18.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index a665343..dacc96f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,15 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= +github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -13,3 +23,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/schema/client.go b/schema/client.go new file mode 100644 index 0000000..961b582 --- /dev/null +++ b/schema/client.go @@ -0,0 +1,18 @@ +package schema + +import ( + "context" + "net/http" + + "github.com/internxt/rclone-adapter/config" +) + +func NewOpenapiClient(baseUrl, token string) (*Client, error) { + return NewClient(baseUrl, + WithRequestEditorFn(func(ctx context.Context, req *http.Request) error { + req.Header.Set("internxt-client", config.ClientName) + req.Header.Set("Authorization", "Bearer "+token) + return nil + }), + ) +} diff --git a/schema/schema.gen.go b/schema/schema.gen.go new file mode 100644 index 0000000..1a7ba65 --- /dev/null +++ b/schema/schema.gen.go @@ -0,0 +1,562 @@ +// Package schema provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT. +package schema + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + openapi_types "github.com/oapi-codegen/runtime/types" +) + +const ( + BearerScopes = "bearer.Scopes" +) + +// Defines values for FileDtoStatus. +const ( + DELETED FileDtoStatus = "DELETED" + EXISTS FileDtoStatus = "EXISTS" + TRASHED FileDtoStatus = "TRASHED" +) + +// CreateFileDto defines model for CreateFileDto. +type CreateFileDto struct { + // Bucket The bucket where the file is stored + Bucket string `json:"bucket"` + + // CreationTime The creation time of the file (optional) + CreationTime *time.Time `json:"creationTime,omitempty"` + + // Date The date associated with the file (optional) + Date *time.Time `json:"date,omitempty"` + + // EncryptVersion The encryption version used for the file + EncryptVersion string `json:"encryptVersion"` + + // FileId The ID of the file (required when size > 0, must not be provided when size = 0) + FileId *string `json:"fileId,omitempty"` + + // FolderUuid The UUID of the folder containing the file + FolderUuid openapi_types.UUID `json:"folderUuid"` + + // ModificationTime The last modification time of the file (optional) + ModificationTime *time.Time `json:"modificationTime,omitempty"` + + // PlainName The plain text name of the file + PlainName string `json:"plainName"` + + // Size The size of the file in bytes + Size float32 `json:"size"` + + // Type The type of the file (optional) + Type *string `json:"type,omitempty"` +} + +// FileDto defines model for FileDto. +type FileDto struct { + Bucket string `json:"bucket"` + CreatedAt time.Time `json:"createdAt"` + CreationTime time.Time `json:"creationTime"` + EncryptVersion string `json:"encryptVersion"` + FileId *string `json:"fileId"` + FolderId float32 `json:"folderId"` + FolderUuid string `json:"folderUuid"` + Id float32 `json:"id"` + ModificationTime time.Time `json:"modificationTime"` + Name string `json:"name"` + PlainName string `json:"plainName"` + Size string `json:"size"` + Status FileDtoStatus `json:"status"` + Type string `json:"type"` + UpdatedAt time.Time `json:"updatedAt"` + UserId float32 `json:"userId"` + Uuid string `json:"uuid"` +} + +// FileDtoStatus defines model for FileDto.Status. +type FileDtoStatus string + +// GetUserLimitDto defines model for GetUserLimitDto. +type GetUserLimitDto struct { + MaxSpaceBytes float32 `json:"maxSpaceBytes"` +} + +// GetUserUsageDto defines model for GetUserUsageDto. +type GetUserUsageDto struct { + Backup float32 `json:"backup"` + Drive float32 `json:"drive"` + Total float32 `json:"total"` +} + +// FileControllerCreateFileJSONRequestBody defines body for FileControllerCreateFile for application/json ContentType. +type FileControllerCreateFileJSONRequestBody = CreateFileDto + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // FileControllerCreateFileWithBody request with any body + FileControllerCreateFileWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + FileControllerCreateFile(ctx context.Context, body FileControllerCreateFileJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UserControllerLimit request + UserControllerLimit(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UserControllerGetUserUsage request + UserControllerGetUserUsage(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) FileControllerCreateFileWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewFileControllerCreateFileRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) FileControllerCreateFile(ctx context.Context, body FileControllerCreateFileJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewFileControllerCreateFileRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UserControllerLimit(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUserControllerLimitRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UserControllerGetUserUsage(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUserControllerGetUserUsageRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewFileControllerCreateFileRequest calls the generic FileControllerCreateFile builder with application/json body +func NewFileControllerCreateFileRequest(server string, body FileControllerCreateFileJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewFileControllerCreateFileRequestWithBody(server, "application/json", bodyReader) +} + +// NewFileControllerCreateFileRequestWithBody generates requests for FileControllerCreateFile with any type of body +func NewFileControllerCreateFileRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/files") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewUserControllerLimitRequest generates requests for UserControllerLimit +func NewUserControllerLimitRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/limit") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUserControllerGetUserUsageRequest generates requests for UserControllerGetUserUsage +func NewUserControllerGetUserUsageRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/users/usage") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // FileControllerCreateFileWithBodyWithResponse request with any body + FileControllerCreateFileWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*FileControllerCreateFileResponse, error) + + FileControllerCreateFileWithResponse(ctx context.Context, body FileControllerCreateFileJSONRequestBody, reqEditors ...RequestEditorFn) (*FileControllerCreateFileResponse, error) + + // UserControllerLimitWithResponse request + UserControllerLimitWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserControllerLimitResponse, error) + + // UserControllerGetUserUsageWithResponse request + UserControllerGetUserUsageWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserControllerGetUserUsageResponse, error) +} + +type FileControllerCreateFileResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *FileDto +} + +// Status returns HTTPResponse.Status +func (r FileControllerCreateFileResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r FileControllerCreateFileResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UserControllerLimitResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetUserLimitDto +} + +// Status returns HTTPResponse.Status +func (r UserControllerLimitResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UserControllerLimitResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UserControllerGetUserUsageResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetUserUsageDto +} + +// Status returns HTTPResponse.Status +func (r UserControllerGetUserUsageResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UserControllerGetUserUsageResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// FileControllerCreateFileWithBodyWithResponse request with arbitrary body returning *FileControllerCreateFileResponse +func (c *ClientWithResponses) FileControllerCreateFileWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*FileControllerCreateFileResponse, error) { + rsp, err := c.FileControllerCreateFileWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseFileControllerCreateFileResponse(rsp) +} + +func (c *ClientWithResponses) FileControllerCreateFileWithResponse(ctx context.Context, body FileControllerCreateFileJSONRequestBody, reqEditors ...RequestEditorFn) (*FileControllerCreateFileResponse, error) { + rsp, err := c.FileControllerCreateFile(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseFileControllerCreateFileResponse(rsp) +} + +// UserControllerLimitWithResponse request returning *UserControllerLimitResponse +func (c *ClientWithResponses) UserControllerLimitWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserControllerLimitResponse, error) { + rsp, err := c.UserControllerLimit(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseUserControllerLimitResponse(rsp) +} + +// UserControllerGetUserUsageWithResponse request returning *UserControllerGetUserUsageResponse +func (c *ClientWithResponses) UserControllerGetUserUsageWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserControllerGetUserUsageResponse, error) { + rsp, err := c.UserControllerGetUserUsage(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseUserControllerGetUserUsageResponse(rsp) +} + +// ParseFileControllerCreateFileResponse parses an HTTP response from a FileControllerCreateFileWithResponse call +func ParseFileControllerCreateFileResponse(rsp *http.Response) (*FileControllerCreateFileResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &FileControllerCreateFileResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest FileDto + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUserControllerLimitResponse parses an HTTP response from a UserControllerLimitWithResponse call +func ParseUserControllerLimitResponse(rsp *http.Response) (*UserControllerLimitResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UserControllerLimitResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetUserLimitDto + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUserControllerGetUserUsageResponse parses an HTTP response from a UserControllerGetUserUsageWithResponse call +func ParseUserControllerGetUserUsageResponse(rsp *http.Response) (*UserControllerGetUserUsageResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UserControllerGetUserUsageResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetUserUsageDto + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/users/limit.go b/users/limit.go index 3b08246..6193f96 100644 --- a/users/limit.go +++ b/users/limit.go @@ -2,42 +2,26 @@ package users import ( "context" - "encoding/json" "fmt" - "io" - "net/http" - "github.com/internxt/rclone-adapter/config" + "github.com/internxt/rclone-adapter/schema" ) -type LimitResponse struct { - MaxSpaceBytes int64 `json:"maxSpaceBytes"` -} - -// GetLimit calls {DRIVE_API_URL}/users/limit and returns the maximum available storage of the account. -func GetLimit(ctx context.Context, cfg *config.Config) (*LimitResponse, error) { - url := cfg.Endpoints.Drive().Users().Limit() - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) +// GetLimit calls GET {DRIVE_API_URL}/users/limit and returns the maximum available storage of the account. +func GetLimit(ctx context.Context, client *schema.Client) (*schema.GetUserLimitDto, error) { + resp, err := client.UserControllerLimit(ctx) if err != nil { - return nil, fmt.Errorf("failed to create get limit request: %w", err) + return nil, fmt.Errorf("failed to get limit: %w", err) } - req.Header.Set("Authorization", "Bearer "+cfg.Token) - resp, err := cfg.HTTPClient.Do(req) + parsed, err := schema.ParseUserControllerLimitResponse(resp) if err != nil { - return nil, fmt.Errorf("failed to execute get limit request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("GET %s returned %d: %s", url, resp.StatusCode, string(body)) + return nil, fmt.Errorf("failed to parse response: %w", err) } - var limit LimitResponse - if err := json.NewDecoder(resp.Body).Decode(&limit); err != nil { - return nil, fmt.Errorf("failed to decode get limit response: %w", err) + if parsed.JSON200 != nil { + return parsed.JSON200, nil } - return &limit, nil + return nil, fmt.Errorf("unexpected response status: %d", resp.StatusCode) } diff --git a/users/usage.go b/users/usage.go index 09ef4c3..1597c5f 100644 --- a/users/usage.go +++ b/users/usage.go @@ -2,42 +2,26 @@ package users import ( "context" - "encoding/json" "fmt" - "io" - "net/http" - "github.com/internxt/rclone-adapter/config" + "github.com/internxt/rclone-adapter/schema" ) -type UsageResponse struct { - Drive int64 `json:"drive"` -} - // GetUsage calls GET {DRIVE_API_URL}/users/usage and returns the account's current usage in bytes. -func GetUsage(ctx context.Context, cfg *config.Config) (*UsageResponse, error) { - url := cfg.Endpoints.Drive().Users().Usage() - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) +func GetUsage(ctx context.Context, client *schema.Client) (*schema.GetUserUsageDto, error) { + resp, err := client.UserControllerGetUserUsage(ctx) if err != nil { - return nil, fmt.Errorf("failed to create get usage request: %w", err) + return nil, fmt.Errorf("failed to get usage: %w", err) } - req.Header.Set("Authorization", "Bearer "+cfg.Token) - resp, err := cfg.HTTPClient.Do(req) + parsed, err := schema.ParseUserControllerGetUserUsageResponse(resp) if err != nil { - return nil, fmt.Errorf("failed to execute get usage request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("GET %s returned %d: %s", url, resp.StatusCode, string(body)) + return nil, fmt.Errorf("failed to parse response: %w", err) } - var usage UsageResponse - if err := json.NewDecoder(resp.Body).Decode(&usage); err != nil { - return nil, fmt.Errorf("failed to decode get usage response: %w", err) + if parsed.JSON200 != nil { + return parsed.JSON200, nil } - return &usage, nil + return nil, fmt.Errorf("unexpected response status: %d", resp.StatusCode) } diff --git a/users/users_test.go b/users/users_test.go index a9204e0..3ba590b 100644 --- a/users/users_test.go +++ b/users/users_test.go @@ -5,100 +5,78 @@ import ( "encoding/json" "net/http" "net/http/httptest" - "strings" "testing" + + "github.com/internxt/rclone-adapter/schema" + "github.com/stretchr/testify/assert" ) func TestGetUsage(t *testing.T) { testCases := []struct { name string - mockResponse UsageResponse + mockResponse schema.GetUserUsageDto mockStatusCode int - expectError bool errorContains string }{ { name: "successful usage retrieval", - mockResponse: UsageResponse{ + mockResponse: schema.GetUserUsageDto{ Drive: 1024 * 1024 * 1024, // 1 GB }, mockStatusCode: http.StatusOK, - expectError: false, }, { name: "zero usage", - mockResponse: UsageResponse{ + mockResponse: schema.GetUserUsageDto{ Drive: 0, }, mockStatusCode: http.StatusOK, - expectError: false, }, { name: "unauthorized - 401", mockStatusCode: http.StatusUnauthorized, - expectError: true, errorContains: "401", }, { name: "server error - 500", mockStatusCode: http.StatusInternalServerError, - expectError: true, errorContains: "500", }, { name: "forbidden - 403", mockStatusCode: http.StatusForbidden, - expectError: true, errorContains: "403", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { - t.Errorf("expected GET request, got %s", r.Method) - } - - authHeader := r.Header.Get("Authorization") - if !strings.HasPrefix(authHeader, "Bearer ") { - t.Error("expected Authorization header with Bearer token") - } - if !strings.Contains(r.URL.Path, "/usage") { - t.Errorf("expected path to contain /usage, got %s", r.URL.Path) - } + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.Header.Get("Authorization"), "Bearer token") + assert.Contains(t, r.URL.Path, "/usage") - w.WriteHeader(tc.mockStatusCode) if tc.mockStatusCode == http.StatusOK { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tc.mockStatusCode) json.NewEncoder(w).Encode(tc.mockResponse) } else { + w.WriteHeader(tc.mockStatusCode) w.Write([]byte("error message")) } })) defer mockServer.Close() - cfg := newTestConfig(mockServer.URL) + client, _ := schema.NewOpenapiClient(mockServer.URL, "token") + usage, err := GetUsage(context.Background(), client) - usage, err := GetUsage(context.Background(), cfg) - - if tc.expectError { - if err == nil { - t.Error("expected error, got nil") - } - if tc.errorContains != "" && !strings.Contains(err.Error(), tc.errorContains) { - t.Errorf("expected error to contain %q, got %q", tc.errorContains, err.Error()) - } + if tc.errorContains != "" { + assert.ErrorContains(t, err, tc.errorContains) } else { - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if usage == nil { - t.Fatal("expected usage response, got nil") - } - if usage.Drive != tc.mockResponse.Drive { - t.Errorf("expected Drive %d, got %d", tc.mockResponse.Drive, usage.Drive) - } + assert.NoError(t, err) + assert.NotNil(t, usage) + assert.Equal(t, tc.mockResponse.Drive, usage.Drive) } }) } @@ -106,112 +84,83 @@ func TestGetUsage(t *testing.T) { func TestGetUsageInvalidJSON(t *testing.T) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte("invalid json response")) })) defer mockServer.Close() - cfg := newTestConfig(mockServer.URL) + client, _ := schema.NewOpenapiClient(mockServer.URL, "token") + _, err := GetUsage(context.Background(), client) - _, err := GetUsage(context.Background(), cfg) - if err == nil { - t.Fatal("expected error for invalid JSON, got nil") - } - if !strings.Contains(err.Error(), "failed to decode") { - t.Errorf("expected error to contain 'failed to decode', got %q", err.Error()) - } + assert.ErrorContains(t, err, "failed to parse response") } func TestGetLimit(t *testing.T) { testCases := []struct { name string - mockResponse LimitResponse + mockResponse schema.GetUserLimitDto mockStatusCode int - expectError bool errorContains string }{ { name: "successful limit retrieval", - mockResponse: LimitResponse{ + mockResponse: schema.GetUserLimitDto{ MaxSpaceBytes: 10 * 1024 * 1024 * 1024, // 10 GB }, mockStatusCode: http.StatusOK, - expectError: false, }, { name: "zero limit", - mockResponse: LimitResponse{ + mockResponse: schema.GetUserLimitDto{ MaxSpaceBytes: 0, }, mockStatusCode: http.StatusOK, - expectError: false, }, { name: "unauthorized - 401", mockStatusCode: http.StatusUnauthorized, - expectError: true, errorContains: "401", }, { name: "server error - 500", mockStatusCode: http.StatusInternalServerError, - expectError: true, errorContains: "500", }, { name: "not found - 404", mockStatusCode: http.StatusNotFound, - expectError: true, - errorContains: "404", + errorContains: "404", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { - t.Errorf("expected GET request, got %s", r.Method) - } - - authHeader := r.Header.Get("Authorization") - if !strings.HasPrefix(authHeader, "Bearer ") { - t.Error("expected Authorization header with Bearer token") - } - - if !strings.Contains(r.URL.Path, "/limit") { - t.Errorf("expected path to contain /limit, got %s", r.URL.Path) - } + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.Header.Get("Authorization"), "Bearer token") + assert.Contains(t, r.URL.Path, "/limit") - w.WriteHeader(tc.mockStatusCode) if tc.mockStatusCode == http.StatusOK { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tc.mockStatusCode) json.NewEncoder(w).Encode(tc.mockResponse) } else { + w.WriteHeader(tc.mockStatusCode) w.Write([]byte("error message")) } })) defer mockServer.Close() - cfg := newTestConfig(mockServer.URL) + client, _ := schema.NewOpenapiClient(mockServer.URL, "token") + limit, err := GetLimit(context.Background(), client) - limit, err := GetLimit(context.Background(), cfg) - - if tc.expectError { - if err == nil { - t.Error("expected error, got nil") - } - if tc.errorContains != "" && !strings.Contains(err.Error(), tc.errorContains) { - t.Errorf("expected error to contain %q, got %q", tc.errorContains, err.Error()) - } + if tc.errorContains != "" { + assert.ErrorContains(t, err, tc.errorContains) } else { - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if limit == nil { - t.Fatal("expected limit response, got nil") - } - if limit.MaxSpaceBytes != tc.mockResponse.MaxSpaceBytes { - t.Errorf("expected MaxSpaceBytes %d, got %d", tc.mockResponse.MaxSpaceBytes, limit.MaxSpaceBytes) - } + assert.NoError(t, err) + assert.NotNil(t, limit) + assert.Equal(t, tc.mockResponse.MaxSpaceBytes, limit.MaxSpaceBytes) } }) } @@ -219,44 +168,26 @@ func TestGetLimit(t *testing.T) { func TestGetLimitInvalidJSON(t *testing.T) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte("invalid json response")) })) defer mockServer.Close() - cfg := newTestConfig(mockServer.URL) + client, _ := schema.NewOpenapiClient(mockServer.URL, "token") + _, err := GetLimit(context.Background(), client) - _, err := GetLimit(context.Background(), cfg) - if err == nil { - t.Fatal("expected error for invalid JSON, got nil") - } - if !strings.Contains(err.Error(), "failed to decode") { - t.Errorf("expected error to contain 'failed to decode', got %q", err.Error()) - } + assert.ErrorContains(t, err, "failed to parse response") } func TestGetUsageHTTPClientError(t *testing.T) { - // Use an invalid URL that will cause the HTTP client to fail - cfg := newTestConfig("http://invalid-host-that-does-not-exist-12345.local") - - _, err := GetUsage(context.Background(), cfg) - if err == nil { - t.Fatal("expected error with invalid host, got nil") - } - if !strings.Contains(err.Error(), "failed to execute") { - t.Errorf("expected error to contain 'failed to execute', got %q", err.Error()) - } + client, _ := schema.NewOpenapiClient("http://invalid-host-that-does-not-exist", "token") + _, err := GetUsage(context.Background(), client) + assert.ErrorContains(t, err, "no such host") } func TestGetLimitHTTPClientError(t *testing.T) { - // Use an invalid URL that will cause the HTTP client to fail - cfg := newTestConfig("http://invalid-host-that-does-not-exist-12345.local") - - _, err := GetLimit(context.Background(), cfg) - if err == nil { - t.Fatal("expected error with invalid host, got nil") - } - if !strings.Contains(err.Error(), "failed to execute") { - t.Errorf("expected error to contain 'failed to execute', got %q", err.Error()) - } + client, _ := schema.NewOpenapiClient("http://invalid-host-that-does-not-exist", "token") + _, err := GetLimit(context.Background(), client) + assert.ErrorContains(t, err, "no such host") }