diff --git a/go.mod b/go.mod index 54dade651c6ae1a26a8368418eae38278148a8df..6ebaaeedfa8b0d377ee0b3e173e7c7b7161e44c3 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( code.vereign.com/gaiax/tsa/golib v0.0.0-20220321093827-5fdf8f34aad9 github.com/kelseyhightower/envconfig v1.4.0 github.com/open-policy-agent/opa v0.38.1 + github.com/stretchr/testify v1.7.0 go.mongodb.org/mongo-driver v1.8.4 go.uber.org/zap v1.21.0 goa.design/goa/v3 v3.7.0 @@ -14,6 +15,7 @@ require ( require ( github.com/OneOfOne/xxhash v1.2.8 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/dimfeld/httptreemux/v5 v5.4.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect @@ -27,6 +29,7 @@ require ( github.com/klauspost/compress v1.13.6 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/smartystreets/assertions v1.2.1 // indirect @@ -47,4 +50,5 @@ require ( golang.org/x/tools v0.1.10 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/internal/regocache/regocache_test.go b/internal/regocache/regocache_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fd63e6f1b76f2ecf9e43bf596ebfaa0d8699fb87 --- /dev/null +++ b/internal/regocache/regocache_test.go @@ -0,0 +1,72 @@ +package regocache_test + +import ( + "context" + "testing" + + "github.com/open-policy-agent/opa/rego" + "github.com/stretchr/testify/assert" + + "code.vereign.com/gaiax/tsa/policy/internal/regocache" + "code.vereign.com/gaiax/tsa/policy/internal/service/policy" +) + +const regoPolicy = ` + package test + + allow { + input.val == 1 + } +` + +func TestNew(t *testing.T) { + cache := regocache.New() + assert.Implements(t, (*policy.RegoCache)(nil), cache) +} + +func TestCache_SetAndGet(t *testing.T) { + q1, err := rego.New( + rego.Module("filename.rego", regoPolicy), + rego.Query("data"), + ).PrepareForEval(context.Background()) + assert.NoError(t, err) + + cache := regocache.New() + cache.Set("query1", &q1) + + q2, ok := cache.Get("query1") + assert.True(t, ok) + assert.Equal(t, q1, *q2) +} + +func TestCache_Purge(t *testing.T) { + q1, err := rego.New( + rego.Module("filename.rego", regoPolicy), + rego.Query("data"), + ).PrepareForEval(context.Background()) + assert.NoError(t, err) + + cache := regocache.New() + cache.Set("query1", &q1) + + cache.Purge() + q2, ok := cache.Get("query1") + assert.False(t, ok) + assert.Nil(t, q2) +} + +func TestCache_PolicyDataChange(t *testing.T) { + q1, err := rego.New( + rego.Module("filename.rego", regoPolicy), + rego.Query("data"), + ).PrepareForEval(context.Background()) + assert.NoError(t, err) + + cache := regocache.New() + cache.Set("query1", &q1) + + cache.PolicyDataChange() + q2, ok := cache.Get("query1") + assert.False(t, ok) + assert.Nil(t, q2) +} diff --git a/internal/service/policy/policyfakes/fake_rego_cache.go b/internal/service/policy/policyfakes/fake_rego_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..4bc89b3dc24a07e9c32b4302f6cc2ea189665680 --- /dev/null +++ b/internal/service/policy/policyfakes/fake_rego_cache.go @@ -0,0 +1,158 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package policyfakes + +import ( + "sync" + + "code.vereign.com/gaiax/tsa/policy/internal/service/policy" + "github.com/open-policy-agent/opa/rego" +) + +type FakeRegoCache struct { + GetStub func(string) (*rego.PreparedEvalQuery, bool) + getMutex sync.RWMutex + getArgsForCall []struct { + arg1 string + } + getReturns struct { + result1 *rego.PreparedEvalQuery + result2 bool + } + getReturnsOnCall map[int]struct { + result1 *rego.PreparedEvalQuery + result2 bool + } + SetStub func(string, *rego.PreparedEvalQuery) + setMutex sync.RWMutex + setArgsForCall []struct { + arg1 string + arg2 *rego.PreparedEvalQuery + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeRegoCache) Get(arg1 string) (*rego.PreparedEvalQuery, bool) { + fake.getMutex.Lock() + ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)] + fake.getArgsForCall = append(fake.getArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.GetStub + fakeReturns := fake.getReturns + fake.recordInvocation("Get", []interface{}{arg1}) + fake.getMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeRegoCache) GetCallCount() int { + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + return len(fake.getArgsForCall) +} + +func (fake *FakeRegoCache) GetCalls(stub func(string) (*rego.PreparedEvalQuery, bool)) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = stub +} + +func (fake *FakeRegoCache) GetArgsForCall(i int) string { + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + argsForCall := fake.getArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeRegoCache) GetReturns(result1 *rego.PreparedEvalQuery, result2 bool) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = nil + fake.getReturns = struct { + result1 *rego.PreparedEvalQuery + result2 bool + }{result1, result2} +} + +func (fake *FakeRegoCache) GetReturnsOnCall(i int, result1 *rego.PreparedEvalQuery, result2 bool) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = nil + if fake.getReturnsOnCall == nil { + fake.getReturnsOnCall = make(map[int]struct { + result1 *rego.PreparedEvalQuery + result2 bool + }) + } + fake.getReturnsOnCall[i] = struct { + result1 *rego.PreparedEvalQuery + result2 bool + }{result1, result2} +} + +func (fake *FakeRegoCache) Set(arg1 string, arg2 *rego.PreparedEvalQuery) { + fake.setMutex.Lock() + fake.setArgsForCall = append(fake.setArgsForCall, struct { + arg1 string + arg2 *rego.PreparedEvalQuery + }{arg1, arg2}) + stub := fake.SetStub + fake.recordInvocation("Set", []interface{}{arg1, arg2}) + fake.setMutex.Unlock() + if stub != nil { + fake.SetStub(arg1, arg2) + } +} + +func (fake *FakeRegoCache) SetCallCount() int { + fake.setMutex.RLock() + defer fake.setMutex.RUnlock() + return len(fake.setArgsForCall) +} + +func (fake *FakeRegoCache) SetCalls(stub func(string, *rego.PreparedEvalQuery)) { + fake.setMutex.Lock() + defer fake.setMutex.Unlock() + fake.SetStub = stub +} + +func (fake *FakeRegoCache) SetArgsForCall(i int) (string, *rego.PreparedEvalQuery) { + fake.setMutex.RLock() + defer fake.setMutex.RUnlock() + argsForCall := fake.setArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeRegoCache) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + fake.setMutex.RLock() + defer fake.setMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeRegoCache) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ policy.RegoCache = new(FakeRegoCache) diff --git a/internal/service/policy/policyfakes/fake_storage.go b/internal/service/policy/policyfakes/fake_storage.go new file mode 100644 index 0000000000000000000000000000000000000000..32bf19186b7c957b17f2c04cde9d745ee6e4edbd --- /dev/null +++ b/internal/service/policy/policyfakes/fake_storage.go @@ -0,0 +1,206 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package policyfakes + +import ( + "context" + "sync" + + "code.vereign.com/gaiax/tsa/policy/internal/service/policy" + "code.vereign.com/gaiax/tsa/policy/internal/storage" +) + +type FakeStorage struct { + PolicyStub func(context.Context, string, string, string) (*storage.Policy, error) + policyMutex sync.RWMutex + policyArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + policyReturns struct { + result1 *storage.Policy + result2 error + } + policyReturnsOnCall map[int]struct { + result1 *storage.Policy + result2 error + } + SetPolicyLockStub func(context.Context, string, string, string, bool) error + setPolicyLockMutex sync.RWMutex + setPolicyLockArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + arg5 bool + } + setPolicyLockReturns struct { + result1 error + } + setPolicyLockReturnsOnCall map[int]struct { + result1 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeStorage) Policy(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*storage.Policy, error) { + fake.policyMutex.Lock() + ret, specificReturn := fake.policyReturnsOnCall[len(fake.policyArgsForCall)] + fake.policyArgsForCall = append(fake.policyArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + stub := fake.PolicyStub + fakeReturns := fake.policyReturns + fake.recordInvocation("Policy", []interface{}{arg1, arg2, arg3, arg4}) + fake.policyMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStorage) PolicyCallCount() int { + fake.policyMutex.RLock() + defer fake.policyMutex.RUnlock() + return len(fake.policyArgsForCall) +} + +func (fake *FakeStorage) PolicyCalls(stub func(context.Context, string, string, string) (*storage.Policy, error)) { + fake.policyMutex.Lock() + defer fake.policyMutex.Unlock() + fake.PolicyStub = stub +} + +func (fake *FakeStorage) PolicyArgsForCall(i int) (context.Context, string, string, string) { + fake.policyMutex.RLock() + defer fake.policyMutex.RUnlock() + argsForCall := fake.policyArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeStorage) PolicyReturns(result1 *storage.Policy, result2 error) { + fake.policyMutex.Lock() + defer fake.policyMutex.Unlock() + fake.PolicyStub = nil + fake.policyReturns = struct { + result1 *storage.Policy + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) PolicyReturnsOnCall(i int, result1 *storage.Policy, result2 error) { + fake.policyMutex.Lock() + defer fake.policyMutex.Unlock() + fake.PolicyStub = nil + if fake.policyReturnsOnCall == nil { + fake.policyReturnsOnCall = make(map[int]struct { + result1 *storage.Policy + result2 error + }) + } + fake.policyReturnsOnCall[i] = struct { + result1 *storage.Policy + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) SetPolicyLock(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 bool) error { + fake.setPolicyLockMutex.Lock() + ret, specificReturn := fake.setPolicyLockReturnsOnCall[len(fake.setPolicyLockArgsForCall)] + fake.setPolicyLockArgsForCall = append(fake.setPolicyLockArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + arg5 bool + }{arg1, arg2, arg3, arg4, arg5}) + stub := fake.SetPolicyLockStub + fakeReturns := fake.setPolicyLockReturns + fake.recordInvocation("SetPolicyLock", []interface{}{arg1, arg2, arg3, arg4, arg5}) + fake.setPolicyLockMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4, arg5) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeStorage) SetPolicyLockCallCount() int { + fake.setPolicyLockMutex.RLock() + defer fake.setPolicyLockMutex.RUnlock() + return len(fake.setPolicyLockArgsForCall) +} + +func (fake *FakeStorage) SetPolicyLockCalls(stub func(context.Context, string, string, string, bool) error) { + fake.setPolicyLockMutex.Lock() + defer fake.setPolicyLockMutex.Unlock() + fake.SetPolicyLockStub = stub +} + +func (fake *FakeStorage) SetPolicyLockArgsForCall(i int) (context.Context, string, string, string, bool) { + fake.setPolicyLockMutex.RLock() + defer fake.setPolicyLockMutex.RUnlock() + argsForCall := fake.setPolicyLockArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 +} + +func (fake *FakeStorage) SetPolicyLockReturns(result1 error) { + fake.setPolicyLockMutex.Lock() + defer fake.setPolicyLockMutex.Unlock() + fake.SetPolicyLockStub = nil + fake.setPolicyLockReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeStorage) SetPolicyLockReturnsOnCall(i int, result1 error) { + fake.setPolicyLockMutex.Lock() + defer fake.setPolicyLockMutex.Unlock() + fake.SetPolicyLockStub = nil + if fake.setPolicyLockReturnsOnCall == nil { + fake.setPolicyLockReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.setPolicyLockReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeStorage) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.policyMutex.RLock() + defer fake.policyMutex.RUnlock() + fake.setPolicyLockMutex.RLock() + defer fake.setPolicyLockMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeStorage) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ policy.Storage = new(FakeStorage) diff --git a/internal/service/policy/service.go b/internal/service/policy/service.go index 8c6d19ea2712a8e1af6edcb43fcbfb5de3d8999e..aff12a10b1ba8603a7d799e0cdc28fc317f63cae 100644 --- a/internal/service/policy/service.go +++ b/internal/service/policy/service.go @@ -12,6 +12,9 @@ import ( "code.vereign.com/gaiax/tsa/policy/internal/storage" ) +//go:generate counterfeiter . Storage +//go:generate counterfeiter . RegoCache + type Storage interface { Policy(ctx context.Context, name, group, version string) (*storage.Policy, error) SetPolicyLock(ctx context.Context, name, group, version string, lock bool) error diff --git a/internal/service/policy/service_test.go b/internal/service/policy/service_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a5eebd2425e15d1088ff4863ebd4130dac8190c4 --- /dev/null +++ b/internal/service/policy/service_test.go @@ -0,0 +1,356 @@ +package policy_test + +import ( + "context" + "testing" + "time" + + "github.com/open-policy-agent/opa/rego" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "code.vereign.com/gaiax/tsa/golib/errors" + goapolicy "code.vereign.com/gaiax/tsa/policy/gen/policy" + "code.vereign.com/gaiax/tsa/policy/internal/service/policy" + "code.vereign.com/gaiax/tsa/policy/internal/service/policy/policyfakes" + "code.vereign.com/gaiax/tsa/policy/internal/storage" +) + +func TestNew(t *testing.T) { + storage := &policyfakes.FakeStorage{} + regocache := &policyfakes.FakeRegoCache{} + svc := policy.New(storage, regocache, zap.NewNop()) + assert.Implements(t, (*goapolicy.Service)(nil), svc) +} + +func TestService_Evaluate(t *testing.T) { + // prepare test policy source code that will be evaluated + testPolicy := `package testgroup.example allow { input.msg == "yes" }` + + // prepare test query that can be retrieved from rego cache + testQuery, err := rego.New( + rego.Module("example.rego", testPolicy), + rego.Query("data.testgroup.example"), + ).PrepareForEval(context.Background()) + assert.NoError(t, err) + + // prepare test request to be used in tests + testReq := func() *goapolicy.EvaluateRequest { + return &goapolicy.EvaluateRequest{ + Group: "testgroup", + PolicyName: "example", + Version: "1.0", + Input: map[string]interface{}{"msg": "yes"}, + } + } + + tests := []struct { + // test input + name string + req *goapolicy.EvaluateRequest + storage policy.Storage + regocache policy.RegoCache + + // expected result + res *goapolicy.EvaluateResult + errkind errors.Kind + errtext string + }{ + { + name: "prepared query is found in cache", + req: testReq(), + regocache: &policyfakes.FakeRegoCache{ + GetStub: func(key string) (*rego.PreparedEvalQuery, bool) { + q := testQuery + return &q, true + }, + }, + res: &goapolicy.EvaluateResult{Result: map[string]interface{}{"allow": true}}, + }, + { + name: "policy is not found", + req: testReq(), + regocache: &policyfakes.FakeRegoCache{ + GetStub: func(key string) (*rego.PreparedEvalQuery, bool) { + return nil, false + }, + }, + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return nil, errors.New(errors.NotFound) + }, + }, + res: nil, + errkind: errors.NotFound, + errtext: "not found", + }, + { + name: "error getting policy from storage", + req: testReq(), + regocache: &policyfakes.FakeRegoCache{ + GetStub: func(key string) (*rego.PreparedEvalQuery, bool) { + return nil, false + }, + }, + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return nil, errors.New("some error") + }, + }, + res: nil, + errkind: errors.Unknown, + errtext: "some error", + }, + { + name: "policy is locked", + req: testReq(), + regocache: &policyfakes.FakeRegoCache{ + GetStub: func(key string) (*rego.PreparedEvalQuery, bool) { + return nil, false + }, + }, + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{Locked: true}, nil + }, + }, + res: nil, + errkind: errors.Forbidden, + errtext: "policy is locked", + }, + { + name: "policy is found in storage and isn't locked", + req: testReq(), + regocache: &policyfakes.FakeRegoCache{ + GetStub: func(key string) (*rego.PreparedEvalQuery, bool) { + return nil, false + }, + }, + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{ + Name: "example", + Group: "testgroup", + Version: "1.0", + Rego: testPolicy, + Locked: false, + LastUpdate: time.Now(), + }, nil + }, + }, + res: &goapolicy.EvaluateResult{Result: map[string]interface{}{"allow": true}}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + svc := policy.New(test.storage, test.regocache, zap.NewNop()) + res, err := svc.Evaluate(context.Background(), test.req) + if err == nil { + assert.Empty(t, test.errtext) + assert.Equal(t, test.res, res) + } else { + e, ok := err.(*errors.Error) + assert.True(t, ok) + + assert.Contains(t, e.Error(), test.errtext) + assert.Equal(t, test.errkind, e.Kind) + assert.Equal(t, test.res, res) + } + }) + } +} + +func TestService_Lock(t *testing.T) { + // prepare test request to be used in tests + testReq := func() *goapolicy.LockRequest { + return &goapolicy.LockRequest{ + Group: "testgroup", + PolicyName: "example", + Version: "1.0", + } + } + + tests := []struct { + name string + req *goapolicy.LockRequest + storage policy.Storage + + errkind errors.Kind + errtext string + }{ + { + name: "policy is not found", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return nil, errors.New(errors.NotFound) + }, + }, + errkind: errors.NotFound, + errtext: "not found", + }, + { + name: "error getting policy from storage", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return nil, errors.New("some error") + }, + }, + errkind: errors.Unknown, + errtext: "some error", + }, + { + name: "policy is already locked", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{Locked: true}, nil + }, + }, + errkind: errors.Forbidden, + errtext: "policy is already locked", + }, + { + name: "fail to lock policy", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{Locked: false}, nil + }, + SetPolicyLockStub: func(ctx context.Context, name, group, version string, lock bool) error { + return errors.New(errors.Internal, "error locking policy") + }, + }, + errkind: errors.Internal, + errtext: "error locking policy", + }, + { + name: "policy is locked successfully", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{Locked: false}, nil + }, + SetPolicyLockStub: func(ctx context.Context, name, group, version string, lock bool) error { + return nil + }, + }, + errtext: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + svc := policy.New(test.storage, nil, zap.NewNop()) + err := svc.Lock(context.Background(), test.req) + if err == nil { + assert.Empty(t, test.errtext) + } else { + e, ok := err.(*errors.Error) + assert.True(t, ok) + + assert.Contains(t, e.Error(), test.errtext) + assert.Equal(t, test.errkind, e.Kind) + } + }) + } +} + +func TestService_Unlock(t *testing.T) { + // prepare test request to be used in tests + testReq := func() *goapolicy.UnlockRequest { + return &goapolicy.UnlockRequest{ + Group: "testgroup", + PolicyName: "example", + Version: "1.0", + } + } + + tests := []struct { + name string + req *goapolicy.UnlockRequest + storage policy.Storage + + errkind errors.Kind + errtext string + }{ + { + name: "policy is not found", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return nil, errors.New(errors.NotFound) + }, + }, + errkind: errors.NotFound, + errtext: "not found", + }, + { + name: "error getting policy from storage", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return nil, errors.New("some error") + }, + }, + errkind: errors.Unknown, + errtext: "some error", + }, + { + name: "policy is unlocked", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{Locked: false}, nil + }, + }, + errkind: errors.Forbidden, + errtext: "policy is unlocked", + }, + { + name: "fail to unlock policy", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{Locked: true}, nil + }, + SetPolicyLockStub: func(ctx context.Context, name, group, version string, lock bool) error { + return errors.New(errors.Internal, "error unlocking policy") + }, + }, + errkind: errors.Internal, + errtext: "error unlocking policy", + }, + { + name: "policy is unlocked successfully", + req: testReq(), + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{Locked: true}, nil + }, + SetPolicyLockStub: func(ctx context.Context, name, group, version string, lock bool) error { + return nil + }, + }, + errtext: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + svc := policy.New(test.storage, nil, zap.NewNop()) + err := svc.Unlock(context.Background(), test.req) + if err == nil { + assert.Empty(t, test.errtext) + } else { + e, ok := err.(*errors.Error) + assert.True(t, ok) + + assert.Contains(t, e.Error(), test.errtext) + assert.Equal(t, test.errkind, e.Kind) + } + }) + } +} diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..bc52e96f2b0ea97cc450e2fefbbb4cc430d1ac5a Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/LICENSE differ diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go new file mode 100644 index 0000000000000000000000000000000000000000..792994785e36ca74c5545a0d93a2cdecda006678 Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/spew/bypass.go differ diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go new file mode 100644 index 0000000000000000000000000000000000000000..205c28d68c474e4497e6aa1ce8b9fdeb260f4586 Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go differ diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go new file mode 100644 index 0000000000000000000000000000000000000000..1be8ce9457612e02a64c01b2321d087ebd6415f2 Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/spew/common.go differ diff --git a/vendor/github.com/davecgh/go-spew/spew/config.go b/vendor/github.com/davecgh/go-spew/spew/config.go new file mode 100644 index 0000000000000000000000000000000000000000..2e3d22f312026ff2c863bbffcbc88b7f6fb942f5 Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/spew/config.go differ diff --git a/vendor/github.com/davecgh/go-spew/spew/doc.go b/vendor/github.com/davecgh/go-spew/spew/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..aacaac6f1e1e936ee0022c00e139756c9bdc2b3e Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/spew/doc.go differ diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go new file mode 100644 index 0000000000000000000000000000000000000000..f78d89fc1f6c454df58cd1e346817db6e30c4299 Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/spew/dump.go differ diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go new file mode 100644 index 0000000000000000000000000000000000000000..b04edb7d7ac278ae0b873a1335f37822a00bfd7c Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/spew/format.go differ diff --git a/vendor/github.com/davecgh/go-spew/spew/spew.go b/vendor/github.com/davecgh/go-spew/spew/spew.go new file mode 100644 index 0000000000000000000000000000000000000000..32c0e338825308f6b9b4d0407aa5682a23e2dc9c Binary files /dev/null and b/vendor/github.com/davecgh/go-spew/spew/spew.go differ diff --git a/vendor/github.com/pmezard/go-difflib/LICENSE b/vendor/github.com/pmezard/go-difflib/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..c67dad612a3dfca2b84599c640798d7be7d46728 Binary files /dev/null and b/vendor/github.com/pmezard/go-difflib/LICENSE differ diff --git a/vendor/github.com/pmezard/go-difflib/difflib/difflib.go b/vendor/github.com/pmezard/go-difflib/difflib/difflib.go new file mode 100644 index 0000000000000000000000000000000000000000..003e99fadb4f189565b409b9509ecf30b752d25a Binary files /dev/null and b/vendor/github.com/pmezard/go-difflib/difflib/difflib.go differ diff --git a/vendor/github.com/stretchr/testify/LICENSE b/vendor/github.com/stretchr/testify/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4b0421cf9ee47908beae4b4648babb75b09ee028 Binary files /dev/null and b/vendor/github.com/stretchr/testify/LICENSE differ diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go new file mode 100644 index 0000000000000000000000000000000000000000..41649d26792461a0e999695e0c91a15d72b5898a Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/assertion_compare.go differ diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go new file mode 100644 index 0000000000000000000000000000000000000000..4dfd1229a8617f401e11efa0ad461447f31c1b3e Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/assertion_format.go differ diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..d2bb0b81778858c364f4b3694c00cdd4c72b1c5b Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl differ diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go new file mode 100644 index 0000000000000000000000000000000000000000..25337a6f07e6e05f3f29e5493cc2ba71cc474abb Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/assertion_forward.go differ diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..188bb9e174397295062da708cc9f5207e2331768 Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl differ diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go new file mode 100644 index 0000000000000000000000000000000000000000..1c3b47182a726afbfb1890c5119144bad1bcf8c9 Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/assertion_order.go differ diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go new file mode 100644 index 0000000000000000000000000000000000000000..bcac4401f57fb271d4a0909e607d56d51c606e59 Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/assertions.go differ diff --git a/vendor/github.com/stretchr/testify/assert/doc.go b/vendor/github.com/stretchr/testify/assert/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..c9dccc4d6cd0aad89a9ecf638d8cde1ea043a37a Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/doc.go differ diff --git a/vendor/github.com/stretchr/testify/assert/errors.go b/vendor/github.com/stretchr/testify/assert/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..ac9dc9d1d6156b64c31ac0b130e7a2b1ca86f06d Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/errors.go differ diff --git a/vendor/github.com/stretchr/testify/assert/forward_assertions.go b/vendor/github.com/stretchr/testify/assert/forward_assertions.go new file mode 100644 index 0000000000000000000000000000000000000000..df189d2348f17a3d16888e2581d2a3b7a9d47e93 Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/forward_assertions.go differ diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go new file mode 100644 index 0000000000000000000000000000000000000000..4ed341dd28934c102aa7a40c74ee24b6555c1db1 Binary files /dev/null and b/vendor/github.com/stretchr/testify/assert/http_assertions.go differ diff --git a/vendor/gopkg.in/yaml.v3/LICENSE b/vendor/gopkg.in/yaml.v3/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2683e4bb1f24c14aa2791e6d48ce0ecf3d8ab756 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/LICENSE differ diff --git a/vendor/gopkg.in/yaml.v3/NOTICE b/vendor/gopkg.in/yaml.v3/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..866d74a7ad79165312a2ce3904b4bdb53e6aedf7 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/NOTICE differ diff --git a/vendor/gopkg.in/yaml.v3/README.md b/vendor/gopkg.in/yaml.v3/README.md new file mode 100644 index 0000000000000000000000000000000000000000..08eb1babddfac3d8f4e006448496d0e0d1f8d720 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/README.md differ diff --git a/vendor/gopkg.in/yaml.v3/apic.go b/vendor/gopkg.in/yaml.v3/apic.go new file mode 100644 index 0000000000000000000000000000000000000000..ae7d049f182ae2419ded608e4c763487c99dff52 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/apic.go differ diff --git a/vendor/gopkg.in/yaml.v3/decode.go b/vendor/gopkg.in/yaml.v3/decode.go new file mode 100644 index 0000000000000000000000000000000000000000..df36e3a30f55508515759037e072f79fc9e9e969 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/decode.go differ diff --git a/vendor/gopkg.in/yaml.v3/emitterc.go b/vendor/gopkg.in/yaml.v3/emitterc.go new file mode 100644 index 0000000000000000000000000000000000000000..0f47c9ca8addf8e9d2e454e02842927ae825d0e9 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/emitterc.go differ diff --git a/vendor/gopkg.in/yaml.v3/encode.go b/vendor/gopkg.in/yaml.v3/encode.go new file mode 100644 index 0000000000000000000000000000000000000000..de9e72a3e638d166e96ceab3d77ce59afe6e6f8a Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/encode.go differ diff --git a/vendor/gopkg.in/yaml.v3/parserc.go b/vendor/gopkg.in/yaml.v3/parserc.go new file mode 100644 index 0000000000000000000000000000000000000000..ac66fccc059e3837d17e2a3a1bec5b6d5c398ab1 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/parserc.go differ diff --git a/vendor/gopkg.in/yaml.v3/readerc.go b/vendor/gopkg.in/yaml.v3/readerc.go new file mode 100644 index 0000000000000000000000000000000000000000..b7de0a89c462af605f889bc46ce165e5d4238add Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/readerc.go differ diff --git a/vendor/gopkg.in/yaml.v3/resolve.go b/vendor/gopkg.in/yaml.v3/resolve.go new file mode 100644 index 0000000000000000000000000000000000000000..64ae888057a5aa24c5a3a6ca0fcb08a06269e3ad Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/resolve.go differ diff --git a/vendor/gopkg.in/yaml.v3/scannerc.go b/vendor/gopkg.in/yaml.v3/scannerc.go new file mode 100644 index 0000000000000000000000000000000000000000..ca0070108f4ebe6a09a222075267e0ffca996e72 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/scannerc.go differ diff --git a/vendor/gopkg.in/yaml.v3/sorter.go b/vendor/gopkg.in/yaml.v3/sorter.go new file mode 100644 index 0000000000000000000000000000000000000000..9210ece7e97232891625ed08c549b92c0e9bb169 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/sorter.go differ diff --git a/vendor/gopkg.in/yaml.v3/writerc.go b/vendor/gopkg.in/yaml.v3/writerc.go new file mode 100644 index 0000000000000000000000000000000000000000..b8a116bf9a22b9911958f44904289a8c6b482bd2 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/writerc.go differ diff --git a/vendor/gopkg.in/yaml.v3/yaml.go b/vendor/gopkg.in/yaml.v3/yaml.go new file mode 100644 index 0000000000000000000000000000000000000000..8cec6da48d3ec4d8858ca622383c75e359faee1f Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/yaml.go differ diff --git a/vendor/gopkg.in/yaml.v3/yamlh.go b/vendor/gopkg.in/yaml.v3/yamlh.go new file mode 100644 index 0000000000000000000000000000000000000000..7c6d0077061933c97979f6c84cb659b17391e1a3 Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/yamlh.go differ diff --git a/vendor/gopkg.in/yaml.v3/yamlprivateh.go b/vendor/gopkg.in/yaml.v3/yamlprivateh.go new file mode 100644 index 0000000000000000000000000000000000000000..e88f9c54aecb54ed42665b2a08b66a4f03d999bc Binary files /dev/null and b/vendor/gopkg.in/yaml.v3/yamlprivateh.go differ diff --git a/vendor/modules.txt b/vendor/modules.txt index 7e675d11d423b11bc6b7fe469edc633414a26d2f..19d0e2d044e2b2340a82df9b249cfe7b182b10fa 100644 Binary files a/vendor/modules.txt and b/vendor/modules.txt differ