feat: add logging and support for custom logger (#36)
* feat: add support for logging * fix linting * chore: remove old APIError in favor of ServerError
This commit is contained in:
committed by
GitHub
parent
95a4ff87ea
commit
e79dcb13f0
@@ -6,6 +6,7 @@ linters:
|
||||
- 'mnd'
|
||||
- 'nlreturn'
|
||||
- 'tagliatelle'
|
||||
- 'ireturn'
|
||||
|
||||
# Temporary
|
||||
- 'cyclop'
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
type {{ .Name }} interface {
|
||||
|
||||
Logger
|
||||
|
||||
{{- range $k, $v := .Functions }}
|
||||
|
||||
{{ if $v.Comment }}// {{ $v.Comment }}{{ end }}
|
||||
|
||||
@@ -40,7 +40,7 @@ type defaultUnifiVersionProvider struct {
|
||||
firmwareUpdateApi string
|
||||
}
|
||||
|
||||
func NewUnifiVersionProvider(firmwareUpdateApi string) UnifiVersionProvider { //nolint:ireturn
|
||||
func NewUnifiVersionProvider(firmwareUpdateApi string) UnifiVersionProvider {
|
||||
return &defaultUnifiVersionProvider{
|
||||
firmwareUpdateApi: firmwareUpdateApi,
|
||||
}
|
||||
|
||||
@@ -94,8 +94,76 @@ if err != nil {
|
||||
|
||||
## Debugging and Logging
|
||||
|
||||
For troubleshooting, it might be useful to enable verbose logging. You can implement an interceptor to log additional
|
||||
details like headers, body content, and timings. This can be enabled conditionally in your application's debug mode.
|
||||
The SDK provides flexible logging capabilities through the `Logger` interface. You can either use the default logger or implement your own custom logger.
|
||||
|
||||
### Using the Default Logger
|
||||
|
||||
The SDK includes a default logger based on [logrus](https://github.com/sirupsen/logrus). You can configure it with different logging levels:
|
||||
|
||||
```go
|
||||
// Configure client with default logger at Debug level
|
||||
config := &unifi.ClientConfig{
|
||||
URL: "https://unifi.localdomain",
|
||||
APIKey: "your-api-key",
|
||||
Logger: unifi.NewDefaultLogger(unifi.DebugLevel),
|
||||
}
|
||||
client, err := unifi.NewClient(config)
|
||||
```
|
||||
|
||||
Available logging levels are:
|
||||
- `unifi.DisabledLevel` - no logging
|
||||
- `unifi.TraceLevel` - most verbose level
|
||||
- `unifi.DebugLevel` - debug information
|
||||
- `unifi.InfoLevel` - default level, informational messages
|
||||
- `unifi.WarnLevel` - warning messages
|
||||
- `unifi.ErrorLevel` - error messages only
|
||||
|
||||
Then `Logger` methods are available to be used within the client:
|
||||
|
||||
```go
|
||||
client.Logger.Trace("Trace message")
|
||||
client.Logger.Tracef("Trace message with %s", "formatting")
|
||||
client.Logger.Debug("Debug message")
|
||||
client.Logger.Debugf("Debug message with %s", "formatting")
|
||||
client.Logger.Info("Info message")
|
||||
client.Logger.Infof("Info message with %s", "formatting")
|
||||
client.Logger.Warn("Warn message")
|
||||
client.Logger.Warnf("Warn message with %s", "formatting")
|
||||
client.Logger.Error("Error message")
|
||||
client.Logger.Errorf("Error message with %s", "formatting")
|
||||
```
|
||||
|
||||
### Custom Logger Implementation
|
||||
|
||||
You can implement your own logger by implementing the `Logger` interface:
|
||||
|
||||
```go
|
||||
type MyCustomLogger struct {
|
||||
// your logger fields
|
||||
}
|
||||
|
||||
// Implement all required methods
|
||||
func (l *MyCustomLogger) Trace(msg string) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Debug(msg string) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Info(msg string) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Error(msg string) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Warn(msg string) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Tracef(format string, args ...interface{}) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Debugf(format string, args ...interface{}) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Infof(format string, args ...interface{}) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Errorf(format string, args ...interface{}) { /* implementation */ }
|
||||
func (l *MyCustomLogger) Warnf(format string, args ...interface{}) { /* implementation */ }
|
||||
|
||||
// Use custom logger in client configuration
|
||||
config := &unifi.ClientConfig{
|
||||
URL: "https://unifi.localdomain",
|
||||
APIKey: "your-api-key",
|
||||
Logger: &MyCustomLogger{},
|
||||
}
|
||||
client, err := unifi.NewClient(config)
|
||||
```
|
||||
|
||||
If no logger is specified in the configuration, the SDK will use the default logger with `Info` level.
|
||||
|
||||
## Advanced Error Handling
|
||||
|
||||
@@ -143,4 +211,4 @@ For more details on contributing, see the [Contributing Guidelines](https://gith
|
||||
---
|
||||
|
||||
This document is intended for advanced users who need deeper control and customization over the UniFi client.
|
||||
For most users, the basic configuration and usage examples should suffice.
|
||||
For most users, the basic configuration and usage examples should suffice.
|
||||
@@ -71,6 +71,10 @@ if err != nil {
|
||||
- `HttpTransportCustomizer` for transport-level customization
|
||||
- `HttpRoundTripperProvider` for complete HTTP client control
|
||||
|
||||
4. **Removed unifi.APIError**:
|
||||
- Old: `unifi.APIError` struct for API errors
|
||||
- New: Standard `unifi.ServerError` struct for API errors
|
||||
|
||||
5. **Additional Features in filipowm/go-unifi**:
|
||||
- Validation modes (Soft, Hard, Disabled)
|
||||
- Request/Response interceptors
|
||||
@@ -90,6 +94,7 @@ if err != nil {
|
||||
})
|
||||
```
|
||||
4. Remove explicit `Login()` calls as they are now handled automatically, unless you use [bare client initialization](./getting_started.md#BareClientInitialization)
|
||||
5. Replace usage of `unifi.APIError` with `unifi.ServerError`
|
||||
|
||||
The rest of your code using the client methods should continue to work as before, as the API methods remain the same.
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ var (
|
||||
|
||||
// determineApiStyle checks the base URL to decide which API style to use and sets the apiPaths accordingly.
|
||||
func (c *client) determineApiStyle() error {
|
||||
c.Debug("Determining API style")
|
||||
ctx, cancel := c.newRequestContext()
|
||||
defer cancel()
|
||||
|
||||
@@ -85,8 +86,10 @@ func (c *client) determineApiStyle() error {
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
c.Debug("Using new style API")
|
||||
c.apiPaths = &NewStyleAPI
|
||||
case http.StatusFound:
|
||||
c.Debug("Using old style API")
|
||||
c.apiPaths = &OldStyleAPI
|
||||
default:
|
||||
return fmt.Errorf("expected 200 or 302 status code, but got: %d", resp.StatusCode)
|
||||
|
||||
1
unifi/client.generated.go
generated
1
unifi/client.generated.go
generated
@@ -8,6 +8,7 @@ import (
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Logger
|
||||
|
||||
// BaseURL returns the base URL of the controller.
|
||||
BaseURL() string
|
||||
|
||||
@@ -14,20 +14,17 @@ import (
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// validationMode represents the mode for request validation.
|
||||
// ValidationMode represents the mode for request validation.
|
||||
// It may be set to "soft", "hard", or "disable". The default is "soft".
|
||||
type validationMode string
|
||||
type ValidationMode int
|
||||
|
||||
const (
|
||||
// SoftValidation indicates that validation errors are logged as warnings but do not prevent the request from proceeding.
|
||||
SoftValidation validationMode = "soft"
|
||||
SoftValidation ValidationMode = iota
|
||||
// HardValidation indicates that validation errors are treated as fatal and will cause the request to be rejected.
|
||||
HardValidation validationMode = "hard"
|
||||
HardValidation
|
||||
// DisableValidation indicates that no validation is performed on the request body.
|
||||
DisableValidation validationMode = "disable"
|
||||
// DefaultValidation is the default validation mode used if none is specified.
|
||||
// Currently set to SoftValidation, but may change to HardValidation in a future major version.
|
||||
DefaultValidation validationMode = SoftValidation // TODO: change to hard in next major version
|
||||
DisableValidation
|
||||
)
|
||||
|
||||
// HttpTransportCustomizer is a function type for customizing the HTTP transport.
|
||||
@@ -73,7 +70,8 @@ type ClientConfig struct {
|
||||
UserAgent string
|
||||
ErrorHandler ResponseErrorHandler
|
||||
UseLocking bool
|
||||
ValidationMode validationMode `validate:"omitempty,oneof=soft hard disable"`
|
||||
ValidationMode ValidationMode
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// Credentials abstracts authentication credentials.
|
||||
@@ -112,12 +110,13 @@ func (u UserPassCredentials) GetPass() string { return u.Password }
|
||||
|
||||
// client represents a UniFi client.
|
||||
type client struct {
|
||||
Logger
|
||||
baseURL *url.URL
|
||||
sysInfo *SysInfo
|
||||
apiPaths *APIPaths
|
||||
timeout time.Duration
|
||||
credentials Credentials
|
||||
validationMode validationMode
|
||||
validationMode ValidationMode
|
||||
useLocking bool
|
||||
|
||||
http *http.Client
|
||||
@@ -168,10 +167,19 @@ func (c *client) Version() string {
|
||||
}
|
||||
|
||||
func newClientFromConfig(config *ClientConfig, v *validator) (*client, error) {
|
||||
var log Logger
|
||||
if config.Logger != nil {
|
||||
log = config.Logger
|
||||
} else {
|
||||
log = NewDefaultLogger(InfoLevel)
|
||||
}
|
||||
log.Info("Initializing new UniFi client")
|
||||
var rt http.RoundTripper
|
||||
var err error
|
||||
config.URL = strings.TrimRight(config.URL, "/")
|
||||
log.Debugf("Connecting to UniFi controller at %s", config.URL)
|
||||
if config.HttpRoundTripperProvider != nil {
|
||||
log.Debug("Using custom HTTP round tripper provider")
|
||||
rt = config.HttpRoundTripperProvider()
|
||||
}
|
||||
if rt == nil {
|
||||
@@ -180,6 +188,7 @@ func newClientFromConfig(config *ClientConfig, v *validator) (*client, error) {
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.VerifySSL},
|
||||
}
|
||||
if config.HttpTransportCustomizer != nil {
|
||||
log.Debug("Customizing HTTP transport")
|
||||
if transport, err = config.HttpTransportCustomizer(transport); err != nil {
|
||||
return nil, fmt.Errorf("failed customizing HTTP transport: %w", err)
|
||||
}
|
||||
@@ -205,14 +214,18 @@ func newClientFromConfig(config *ClientConfig, v *validator) (*client, error) {
|
||||
var credentials Credentials
|
||||
|
||||
if config.APIKey != "" {
|
||||
log.Debug("Using API key authentication")
|
||||
credentials = APIKeyCredentials{APIKey: config.APIKey}
|
||||
interceptors = append(interceptors, &APIKeyAuthInterceptor{apiKey: config.APIKey})
|
||||
} else {
|
||||
log.Debug("Using user/pass authentication")
|
||||
credentials = UserPassCredentials{User: config.User, Password: config.Password}
|
||||
interceptors = append(interceptors, &CSRFInterceptor{})
|
||||
}
|
||||
if len(config.UserAgent) == 0 {
|
||||
config.UserAgent = defaultUserAgent
|
||||
} else {
|
||||
log.Debugf("Using custom User-Agent header: %s", config.UserAgent)
|
||||
}
|
||||
interceptors = append(interceptors, &DefaultHeadersInterceptor{headers: map[string]string{
|
||||
UserAgentHeader: config.UserAgent,
|
||||
@@ -221,13 +234,13 @@ func newClientFromConfig(config *ClientConfig, v *validator) (*client, error) {
|
||||
}})
|
||||
var errorHandler ResponseErrorHandler
|
||||
if config.ErrorHandler != nil {
|
||||
log.Debug("Using custom response error handler")
|
||||
errorHandler = config.ErrorHandler
|
||||
} else {
|
||||
log.Debug("Using default response error handler")
|
||||
errorHandler = &DefaultResponseErrorHandler{}
|
||||
}
|
||||
if config.ValidationMode == "" {
|
||||
config.ValidationMode = DefaultValidation
|
||||
}
|
||||
log.Tracef("Validation mode: %d", config.ValidationMode)
|
||||
u := &client{
|
||||
baseURL: baseURL,
|
||||
timeout: config.Timeout,
|
||||
@@ -239,6 +252,7 @@ func newClientFromConfig(config *ClientConfig, v *validator) (*client, error) {
|
||||
errorHandler: errorHandler,
|
||||
lock: sync.Mutex{},
|
||||
validator: v,
|
||||
Logger: log,
|
||||
}
|
||||
for _, interceptor := range config.Interceptors {
|
||||
u.AddInterceptor(&interceptor)
|
||||
@@ -262,6 +276,7 @@ func NewClient(config *ClientConfig) (Client, error) { //nolint: ireturn
|
||||
return c, fmt.Errorf("failed getting server info: %w", err)
|
||||
} else {
|
||||
c.sysInfo = sysInfo
|
||||
c.Debugf("Connected to UniFi controller\nversion: %s; name: %s; build: %s; hostname: %s", sysInfo.Version, sysInfo.Name, sysInfo.Build, sysInfo.Hostname)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
@@ -296,8 +311,10 @@ func newBareClient(config *ClientConfig) (*client, error) {
|
||||
// It returns an error if the authentication process fails.
|
||||
func (c *client) Login() error {
|
||||
if c.credentials.IsAPIKey() {
|
||||
c.Trace("API key authentication; skipping login")
|
||||
return nil
|
||||
}
|
||||
c.Trace("Logging in with user/pass credentials")
|
||||
|
||||
ctx, cancel := c.newRequestContext()
|
||||
defer cancel()
|
||||
|
||||
115
unifi/logging.go
Normal file
115
unifi/logging.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package unifi
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Trace(format string)
|
||||
Debug(format string)
|
||||
Info(format string)
|
||||
Error(format string)
|
||||
Warn(format string)
|
||||
Tracef(format string, args ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
type LoggingLevel int
|
||||
|
||||
const (
|
||||
DisabledLevel LoggingLevel = iota
|
||||
TraceLevel
|
||||
DebugLevel
|
||||
InfoLevel
|
||||
WarnLevel
|
||||
ErrorLevel
|
||||
)
|
||||
|
||||
func NewDefaultLogger(level LoggingLevel) Logger {
|
||||
l := logrus.New()
|
||||
var logrusLevel logrus.Level
|
||||
switch level {
|
||||
case DisabledLevel:
|
||||
return &noopLogger{}
|
||||
case TraceLevel:
|
||||
logrusLevel = logrus.TraceLevel
|
||||
case DebugLevel:
|
||||
logrusLevel = logrus.DebugLevel
|
||||
case InfoLevel:
|
||||
logrusLevel = logrus.InfoLevel
|
||||
case WarnLevel:
|
||||
logrusLevel = logrus.WarnLevel
|
||||
case ErrorLevel:
|
||||
logrusLevel = logrus.ErrorLevel
|
||||
default:
|
||||
logrusLevel = logrus.InfoLevel
|
||||
}
|
||||
l.SetLevel(logrusLevel)
|
||||
l.SetFormatter(&logrus.TextFormatter{
|
||||
DisableTimestamp: true,
|
||||
DisableLevelTruncation: true,
|
||||
FullTimestamp: false,
|
||||
ForceColors: true,
|
||||
})
|
||||
return &defaultLogger{l}
|
||||
}
|
||||
|
||||
type noopLogger struct{}
|
||||
|
||||
func (l *noopLogger) Trace(msg string) {}
|
||||
func (l *noopLogger) Debug(msg string) {}
|
||||
func (l *noopLogger) Info(msg string) {}
|
||||
func (l *noopLogger) Error(msg string) {}
|
||||
func (l *noopLogger) Warn(msg string) {}
|
||||
func (l *noopLogger) Tracef(format string, args ...interface{}) {}
|
||||
func (l *noopLogger) Debugf(format string, args ...interface{}) {}
|
||||
func (l *noopLogger) Infof(format string, args ...interface{}) {}
|
||||
func (l *noopLogger) Errorf(format string, args ...interface{}) {}
|
||||
func (l *noopLogger) Warnf(format string, args ...interface{}) {}
|
||||
|
||||
type defaultLogger struct {
|
||||
*logrus.Logger
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Trace(msg string) {
|
||||
l.Logger.Trace(msg)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Debug(msg string) {
|
||||
l.Logger.Debug(msg)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Info(msg string) {
|
||||
l.Logger.Info(msg)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Error(msg string) {
|
||||
l.Logger.Error(msg)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Warn(msg string) {
|
||||
l.Logger.Warn(msg)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Tracef(format string, args ...interface{}) {
|
||||
l.Logger.Tracef(format, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Debugf(format string, args ...interface{}) {
|
||||
l.Logger.Debugf(format, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Infof(format string, args ...interface{}) {
|
||||
l.Logger.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Errorf(format string, args ...interface{}) {
|
||||
l.Logger.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Warnf(format string, args ...interface{}) {
|
||||
l.Logger.Warnf(format, args...)
|
||||
}
|
||||
@@ -39,12 +39,12 @@ func (c *client) buildRequestURL(apiPath string) (*url.URL, error) {
|
||||
// validateRequestBody validates the request body if validation is enabled.
|
||||
func (c *client) validateRequestBody(reqBody interface{}) error {
|
||||
if reqBody != nil && c.validationMode != DisableValidation {
|
||||
c.Trace("Validating request body")
|
||||
if err := c.validator.Validate(reqBody); err != nil {
|
||||
err = fmt.Errorf("failed validating request body: %w", err)
|
||||
if c.validationMode == HardValidation {
|
||||
return err
|
||||
return fmt.Errorf("failed validating request body: %w", err)
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
c.Warnf("failed validating request body: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ func (c *client) newRequestContext() (context.Context, context.CancelFunc) {
|
||||
// It validates the request body, applies interceptors, and decodes the HTTP response into respBody if provided.
|
||||
// It returns an error if the request or response handling fails.
|
||||
func (c *client) Do(ctx context.Context, method, apiPath string, reqBody interface{}, respBody interface{}) error {
|
||||
c.Tracef("Performing request: %s %s", method, apiPath)
|
||||
if err := c.validateRequestBody(reqBody); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,6 +77,7 @@ func (c *client) Do(ctx context.Context, method, apiPath string, reqBody interfa
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create request URL: %w", err)
|
||||
}
|
||||
c.Debugf("Executing request: %s %s", method, url.String())
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, url.String(), reqReader)
|
||||
if err != nil {
|
||||
@@ -84,8 +86,10 @@ func (c *client) Do(ctx context.Context, method, apiPath string, reqBody interfa
|
||||
|
||||
if c.useLocking {
|
||||
c.lock.Lock()
|
||||
c.Trace("Acquired lock fo request")
|
||||
defer c.lock.Unlock()
|
||||
}
|
||||
c.Trace("Executing request interceptors")
|
||||
for _, interceptor := range c.interceptors {
|
||||
if err := interceptor.InterceptRequest(req); err != nil {
|
||||
return err
|
||||
@@ -98,20 +102,24 @@ func (c *client) Do(ctx context.Context, method, apiPath string, reqBody interfa
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
c.Trace("Executing response interceptors")
|
||||
for _, interceptor := range c.interceptors {
|
||||
if err := interceptor.InterceptResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.Trace("Checking for errors in response")
|
||||
if err := c.errorHandler.HandleError(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if respBody == nil || resp.ContentLength == 0 {
|
||||
c.Trace("No response body to decode")
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Trace("Decoding response body")
|
||||
err = json.NewDecoder(resp.Body).Decode(respBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode body: %s %s %w", method, apiPath, err)
|
||||
|
||||
@@ -117,6 +117,7 @@ func (c *client) getOldSysInfo(ctx context.Context) (*SysInfo, error) {
|
||||
|
||||
// GetSystemInformation retrieves system information, trying the new API first and falling back to the old API if necessary.
|
||||
func (c *client) GetSystemInformation() (*SysInfo, error) {
|
||||
c.Trace("Reading system information")
|
||||
ctx, cancel := c.newRequestContext()
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -10,26 +10,6 @@ import (
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
// TODO old-style error handling to be removed in future versions.
|
||||
type APIError struct {
|
||||
RC string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (err *APIError) Error() string {
|
||||
return err.Message
|
||||
}
|
||||
|
||||
func (err *APIError) Is(target error) bool {
|
||||
var apiError *APIError
|
||||
if errors.As(target, &apiError) {
|
||||
if err.RC == apiError.RC && err.Message == apiError.Message {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
RC string `json:"rc"`
|
||||
Message string `json:"msg"`
|
||||
@@ -37,9 +17,9 @@ type Meta struct {
|
||||
|
||||
func (m *Meta) error() error {
|
||||
if m.RC != "ok" {
|
||||
return &APIError{
|
||||
RC: m.RC,
|
||||
Message: m.Message,
|
||||
return &ServerError{
|
||||
ErrorCode: m.RC,
|
||||
Message: m.Message,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -488,17 +488,16 @@ func TestUrlValidation(t *testing.T) {
|
||||
func TestValidationModeValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := []struct {
|
||||
validationMode validationMode
|
||||
expectedError string
|
||||
validationMode ValidationMode
|
||||
}{
|
||||
{SoftValidation, ""},
|
||||
{HardValidation, ""},
|
||||
{DisableValidation, ""},
|
||||
{"invalid", "must be one of"},
|
||||
{SoftValidation},
|
||||
{HardValidation},
|
||||
{DisableValidation},
|
||||
{99},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(string(tc.validationMode), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%d", tc.validationMode), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// given
|
||||
cc := &ClientConfig{
|
||||
@@ -511,12 +510,6 @@ func TestValidationModeValidation(t *testing.T) {
|
||||
|
||||
// when
|
||||
err = v.Validate(cc)
|
||||
|
||||
// then
|
||||
if tc.expectedError != "" {
|
||||
require.ErrorContains(t, err, tc.expectedError)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -541,7 +534,7 @@ type validateableBody struct {
|
||||
func TestValidationModes(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := []struct {
|
||||
validationMode validationMode
|
||||
validationMode ValidationMode
|
||||
expectedError string
|
||||
expectRequest bool
|
||||
}{
|
||||
@@ -551,7 +544,7 @@ func TestValidationModes(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(string(tc.validationMode), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%d", tc.validationMode), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
a := assert.New(t)
|
||||
// given
|
||||
@@ -843,11 +836,14 @@ func TestMarshalRequestValid(t *testing.T) {
|
||||
func TestLoginWithAPIKeyDirect(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Create a client manually with the APIKey set.
|
||||
c := &client{
|
||||
credentials: APIKeyCredentials{APIKey: "abc"},
|
||||
}
|
||||
err := c.Login()
|
||||
assert.NoError(t, err)
|
||||
|
||||
c, err := newBareClient(&ClientConfig{
|
||||
APIKey: "abc",
|
||||
URL: testUrl,
|
||||
})
|
||||
require.Error(t, err)
|
||||
err = c.Login()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHttpTransportCustomizerError(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user