Skip to content

Commit

Permalink
added metric listener, provide related unit tests (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
kristinapathak authored Jun 2, 2021
1 parent 41ed346 commit 4a50de2
Show file tree
Hide file tree
Showing 6 changed files with 462 additions and 7 deletions.
9 changes: 6 additions & 3 deletions basculechecks/provide.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ import (

// ProvideMetricValidator is an uber fx Provide() function that builds a
// MetricValidator given the dependencies needed.
func ProvideMetricValidator() fx.Option {
func ProvideMetricValidator(optional bool) fx.Option {
return fx.Provide(
fx.Annotated{
Name: "bascule_validator_capabilities",
Target: func(in MetricValidatorIn) (bascule.Validator, error) {
if optional && in.Checker == nil {
return nil, nil
}
return NewMetricValidator(in.Checker, &in.Measures, in.Options...)
},
},
Expand All @@ -45,7 +48,7 @@ func ProvideCapabilitiesMapValidator(key string) fx.Option {
arrange.UnmarshalKey(key, CapabilitiesMapConfig{}),
NewCapabilitiesMap,
),
ProvideMetricValidator(),
ProvideMetricValidator(false),
)
}

Expand All @@ -58,6 +61,6 @@ func ProvideRegexCapabilitiesValidator(key string) fx.Option {
arrange.UnmarshalKey(key, CapabilitiesValidatorConfig{}),
NewCapabilitiesValidator,
),
ProvideMetricValidator(),
ProvideMetricValidator(true),
)
}
89 changes: 89 additions & 0 deletions basculechecks/provide_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Copyright 2021 Comcast Cable Communications Management, LLC
*
* 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 basculechecks

import (
"context"
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xmidt-org/bascule"
"github.com/xmidt-org/touchstone"
"go.uber.org/fx"
)

func TestProvideMetricValidator(t *testing.T) {
type In struct {
fx.In
V bascule.Validator `name:"bascule_validator_capabilities"`
}
tests := []struct {
description string
optional bool
expectedErr error
}{
{
description: "Optional Success",
optional: true,
expectedErr: nil,
},
{
description: "Required Failure",
optional: false,
expectedErr: ErrNilChecker,
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
var result bascule.Validator
app := fx.New(
touchstone.Provide(),
ProvideMetrics(),
fx.Provide(
func() CapabilitiesChecker {
return nil
},
),
ProvideMetricValidator(tc.optional),
fx.Invoke(
func(in In) {
result = in.V
},
),
)
app.Start(context.Background())
defer app.Stop(context.Background())
err := app.Err()
assert.Nil(result)
if tc.expectedErr == nil {
assert.NoError(err)
return
}
require.Error(err)
assert.True(strings.Contains(err.Error(), tc.expectedErr.Error()),
fmt.Errorf("error [%v] doesn't contain error [%v]",
err, tc.expectedErr),
)
})
}
}
6 changes: 5 additions & 1 deletion basculehttp/metricListener.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const (
defaultServer = "primary"
)

var (
ErrNilMeasures = errors.New("measures cannot be nil")
)

// MetricListener keeps track of request authentication and authorization using
// metrics. A counter is updated on success and failure to reflect the outcome
// and reason for failure, if applicable. MetricListener implements the Listener
Expand Down Expand Up @@ -92,7 +96,7 @@ func WithServer(s string) Option {
// nil.
func NewMetricListener(m *AuthValidationMeasures, options ...Option) (*MetricListener, error) {
if m == nil {
return nil, errors.New("measures cannot be nil")
return nil, ErrNilMeasures
}

listener := MetricListener{
Expand Down
190 changes: 189 additions & 1 deletion basculehttp/metricListener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,192 @@

package basculehttp

// TODO: add metric listener tests here.
import (
"errors"
"fmt"
"testing"

"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/xmidt-org/bascule"
"github.com/xmidt-org/touchstone/touchtest"
)

func TestNewMetricListener(t *testing.T) {
m := &AuthValidationMeasures{}
s := "testserver"
tests := []struct {
description string
measures *AuthValidationMeasures
options []Option
expectedMetricListener *MetricListener
expectedErr error
}{
{
description: "Success",
measures: m,
options: []Option{
WithServer(s),
WithServer(""),
},
expectedMetricListener: &MetricListener{
server: s,
measures: m,
},
},
{
description: "Success with defaults",
measures: m,
expectedMetricListener: &MetricListener{
server: defaultServer,
measures: m,
},
},
{
description: "Nil measures error",
expectedErr: ErrNilMeasures,
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
ml, err := NewMetricListener(tc.measures, tc.options...)
assert.Equal(tc.expectedMetricListener, ml)
if tc.expectedErr == nil {
assert.NoError(err)
return
}
assert.True(errors.Is(err, tc.expectedErr),
fmt.Errorf("error [%v] doesn't contain error [%v] in its err chain",
err, tc.expectedErr),
)
})
}
}

func TestOnAuthenticated(t *testing.T) {
server := "testserver"
tests := []struct {
description string
token bascule.Token
expectedOutcome string
}{
{
description: "Success",
token: bascule.NewToken("test", "princ", nil),
expectedOutcome: AcceptedOutcome,
},
{
description: "Success with empty token",
expectedOutcome: EmptyOutcome,
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
testAssert := touchtest.New(t)

// set up metric stuff.
expectedRegistry := prometheus.NewPedanticRegistry()
expectedCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "testCounter",
Help: "testCounter",
},
[]string{ServerLabel, OutcomeLabel},
)
expectedRegistry.Register(expectedCounter)
expectedCounter.With(prometheus.Labels{
ServerLabel: server,
OutcomeLabel: tc.expectedOutcome,
}).Inc()
actualRegistry := prometheus.NewPedanticRegistry()
mockMeasures := AuthValidationMeasures{
ValidationOutcome: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "testCounter",
Help: "testCounter",
},
[]string{ServerLabel, OutcomeLabel},
),
}
actualRegistry.MustRegister(mockMeasures.ValidationOutcome)

m := &MetricListener{
server: server,
measures: &mockMeasures,
}
m.OnAuthenticated(bascule.Authentication{Token: tc.token})
testAssert.Expect(expectedRegistry)
assert.True(testAssert.GatherAndCompare(actualRegistry))
})
}
}
func TestOnErrorResponse(t *testing.T) {
server := "testserver"
tests := []struct {
description string
reason ErrorResponseReason
}{
{
description: "Checks failed",
reason: ChecksFailed,
},
{
description: "Get URL failed",
reason: GetURLFailed,
},
{
description: "Key not supported",
reason: KeyNotSupported,
},
{
description: "Negative unknown reason",
reason: -1,
},
{
description: "Big number unknown reason",
reason: 10000000,
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
testAssert := touchtest.New(t)

// set up metric stuff.
expectedRegistry := prometheus.NewPedanticRegistry()
expectedCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "testCounter",
Help: "testCounter",
},
[]string{ServerLabel, OutcomeLabel},
)
expectedRegistry.Register(expectedCounter)
expectedCounter.With(prometheus.Labels{
ServerLabel: server,
OutcomeLabel: tc.reason.String(),
}).Inc()
actualRegistry := prometheus.NewPedanticRegistry()
mockMeasures := AuthValidationMeasures{
ValidationOutcome: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "testCounter",
Help: "testCounter",
},
[]string{ServerLabel, OutcomeLabel},
),
}
actualRegistry.MustRegister(mockMeasures.ValidationOutcome)

m := &MetricListener{
server: server,
measures: &mockMeasures,
}
m.OnErrorResponse(tc.reason, errors.New("testing error"))
testAssert.Expect(expectedRegistry)
assert.True(testAssert.GatherAndCompare(actualRegistry))
})
}
}
2 changes: 1 addition & 1 deletion basculehttp/provide.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func ProvideBasicAuth(key string) fx.Option {
ProvideBasicTokenFactory(key),
fx.Provide(
fx.Annotated{
Group: "primary_bascule_enforcer_options",
Group: "bascule_enforcer_options",
Target: func() EOption {
return WithRules(BasicAuthorization, basculechecks.AllowAll())
},
Expand Down
Loading

0 comments on commit 4a50de2

Please sign in to comment.