-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[A2-802] CreateRule projects API #410
Conversation
221b135
to
e8d8281
Compare
3f06bf0
to
dacc4e6
Compare
dacc4e6
to
659f032
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a few questions, looks good!
} | ||
|
||
var apiToStorageConditionAttributes map[api.ProjectRuleConditionTypes]storage.ConditionAttribute | ||
var onceReverseConditionAttributesMapping sync.Once |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does sync.Once
do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://golang.org/pkg/sync/#Once It's being used to populate the map apiToStorageConditionAttributes
into memory exactly one time. Once will ensure it's only populated once instead of on every call so it's just cached.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly! It's the lazy way for not having to maintain both mappings manually. Only A -> B is hardcoded, B -> A is initialized once in the way @tylercloke described.
"Conditions": gen.SliceOf(conditionsGen), | ||
}) | ||
|
||
params := gopter.DefaultTestParametersWithSeed(seed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does gopter
do for us?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The readme isn't the best, but it looks like it can be used to generate a bunch of random structs for tested based on possible values for the structs. What it looks like we are using it for here is having it generate lots of different combinations of all the conditions possibilities (since we have a lot of different condition types) so we can test CreateRule
against many possible different condition inputs without manually managing how they are generated.
package chef.automate.api.iam.v2beta; | ||
option go_package = "github.com/chef/automate/components/automate-gateway/api/iam/v2beta/common"; | ||
|
||
enum RuleType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for splitting this out! we probably should do the same for roles one of these days
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use of gopter looks cool for cases where we have a large set of possible inputs to our APIs!
} | ||
|
||
var apiToStorageConditionAttributes map[api.ProjectRuleConditionTypes]storage.ConditionAttribute | ||
var onceReverseConditionAttributesMapping sync.Once |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://golang.org/pkg/sync/#Once It's being used to populate the map apiToStorageConditionAttributes
into memory exactly one time. Once will ensure it's only populated once instead of on every call so it's just cached.
"Conditions": gen.SliceOf(conditionsGen), | ||
}) | ||
|
||
params := gopter.DefaultTestParametersWithSeed(seed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The readme isn't the best, but it looks like it can be used to generate a bunch of random structs for tested based on possible values for the structs. What it looks like we are using it for here is having it generate lots of different combinations of all the conditions possibilities (since we have a lot of different condition types) so we can test CreateRule
against many possible different condition inputs without manually managing how they are generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work; some comments and questions below.
@@ -125,3 +132,16 @@ message Condition { | |||
ProjectRuleConditionTypes type = 1; | |||
repeated string values = 2; | |||
} | |||
|
|||
// CreateRuleReq subsumes ProjectRule, adding id/project/name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it did, but looks like it is now an out-of-date comment. Perhaps "CreateRuleReq mirrors ProjectRule, adding validation" ?
@@ -371,9 +371,14 @@ func addProjectToStore(t *testing.T, store *cache.Cache, id, name string, projTy | |||
} | |||
|
|||
func setupProjects(t *testing.T) (api.ProjectsClient, *cache.Cache, *mockEventServiceClient) { | |||
cl, ca, _, mc, _ := setupProjectsAndRules(t) | |||
return cl, ca, mc | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add blank line after this
cl, ca, _, mc, _ := setupProjectsAndRules(t) | ||
return cl, ca, mc | ||
} | ||
func setupProjectsAndRules(t *testing.T) (api.ProjectsClient, *cache.Cache, *cache.Cache, *mockEventServiceClient, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Odd place to break the line; consider:
func setupProjectsAndRules(t *testing.T) (
api.ProjectsClient, *cache.Cache, *cache.Cache, *mockEventServiceClient, int64) {
t.Helper() | ||
ctx := context.Background() | ||
prng.Seed(t) | ||
seed := prng.GenSeed(t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the seeding change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because it's controlling randomness in gopter
@@ -371,9 +371,14 @@ func addProjectToStore(t *testing.T, store *cache.Cache, id, name string, projTy | |||
} | |||
|
|||
func setupProjects(t *testing.T) (api.ProjectsClient, *cache.Cache, *mockEventServiceClient) { | |||
cl, ca, _, mc, _ := setupProjectsAndRules(t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does setupProjectsAndRules
return arguments that are never used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used for setupProjects
. They are used for setupRules
. 🤷♂ We could clean this up, I suppose. But I don't find it that terrible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, still used to thinking about private to namespace means private to file, but not true in Go. 👍
}, | ||
}, | ||
}, | ||
}, resp) // FIXME |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What needs fixing?
}}, | ||
// happy path | ||
{"with valid rule data, returns no error and creates the rule in storage", func(t *testing.T) { | ||
resp, err := cl.CreateRule(ctx, &api.CreateRuleReq{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was curious whether this reduced form would work, but it fails. Any thoughts on why?
ruleReq := &api.CreateRuleReq{
Id: "any-name",
Name: "any name",
ProjectId: "foo",
Type: api.ProjectRuleTypes_NODE,
Conditions: []*api.Condition{
{
Type: api.ProjectRuleConditionTypes_CHEF_ORGS,
Values: []string{"chef"},
},
},
}
resp, err := cl.CreateRule(ctx, ruleReq)
assert.NoError(t, err)
rule := api.ProjectRule(*ruleReq)
assert.Equal(t, &api.CreateRuleResp{
Rule: &rule,
}, resp)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't dive in deeper, but with this:
diff --git a/components/authz-service/server/v2/rules_test.go b/components/authz-service/server/v2/rules_test.go
index 08a82b6c..c1be18cf 100644
--- a/components/authz-service/server/v2/rules_test.go
+++ b/components/authz-service/server/v2/rules_test.go
@@ -5,6 +5,7 @@ import (
"math/rand"
"testing"
+ "github.com/kylelemons/godebug/pretty"
cache "github.com/patrickmn/go-cache"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
@@ -103,7 +104,8 @@ func TestCreateRule(t *testing.T) {
}},
// happy path
{"with valid rule data, returns no error and creates the rule in storage", func(t *testing.T) {
- resp, err := cl.CreateRule(ctx, &api.CreateRuleReq{
+
+ ruleReq := &api.CreateRuleReq{
Id: "any-name",
Name: "any name",
ProjectId: "foo",
@@ -114,22 +116,16 @@ func TestCreateRule(t *testing.T) {
Values: []string{"chef"},
},
},
- })
+ }
+ actual, err := cl.CreateRule(ctx, ruleReq)
assert.NoError(t, err)
- assert.Equal(t, &api.CreateRuleResp{
- Rule: &api.ProjectRule{
- Id: "any-name",
- Name: "any name",
- ProjectId: "foo",
- Type: api.ProjectRuleTypes_NODE,
- Conditions: []*api.Condition{
- {
- Type: api.ProjectRuleConditionTypes_CHEF_ORGS,
- Values: []string{"chef"},
- },
- },
- },
- }, resp)
+ rule := api.ProjectRule(*ruleReq)
+ expected := &api.CreateRuleResp{
+ Rule: &rule,
+ }
+ if !assert.Equal(t, expected, actual) {
+ t.Log(pretty.Compare(expected, actual))
+ }
}},
}
...we get:
--- FAIL: TestCreateRule/with_valid_rule_data,_returns_no_error_and_creates_the_rule_in_storage (0.00s)
Error Trace: rules_test.go:126
Error: Not equal:
expected: &v2.CreateRuleResp{Rule:(*v2.ProjectRule)(0xc000192690), XXX_NoUnkeyedLiteral:struct {}{}, XXX_unrecognized:[]uint8(nil), XXX_sizecache:0}
actual : &v2.CreateRuleResp{Rule:(*v2.ProjectRule)(0xc000192230), XXX_NoUnkeyedLiteral:struct {}{}, XXX_unrecognized:[]uint8(nil), XXX_sizecache:0}
Diff:
Test: TestCreateRule/with_valid_rule_data,_returns_no_error_and_creates_the_rule_in_storage
rules_test.go:127: {
Rule: {
Id: "any-name",
ProjectId: "foo",
Name: "any name",
Type: 0,
Conditions: [
{
Type: 1,
Values: [
"chef",
],
XXX_NoUnkeyedLiteral: {
},
XXX_unrecognized: [
],
- XXX_sizecache: 8,
+ XXX_sizecache: 0,
},
],
XXX_NoUnkeyedLiteral: {
},
XXX_unrecognized: [
],
- XXX_sizecache: 35,
+ XXX_sizecache: 0,
},
XXX_NoUnkeyedLiteral: {
},
XXX_unrecognized: [
],
XXX_sizecache: 0,
}
So, the trivial answer is: the XXX_sizecache
fields don't match. 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... I did not see the expanded diff when I ran it, just this. Any idea why my output is different?
expected: &v2.CreateRuleResp{Rule:(*v2.ProjectRule)(0xc0001930a0), XXX_NoUnkeyedLiteral:struct {}{}, XXX_unrecognized:[]uint8(nil), XXX_sizecache:0}
actual : &v2.CreateRuleResp{Rule:(*v2.ProjectRule)(0xc000192bd0), XXX_NoUnkeyedLiteral:struct {}{}, XXX_unrecognized:[]uint8(nil), XXX_sizecache:0}
Diff:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For more information please reread -- my comment includes the diff of what I had to do to see that, too ☝️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Except it does not include the actual command; "diff --git..." is output... Am I missing something...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry that was the output of git diff
.
"reflect" | ||
"testing" | ||
|
||
"github.com/leanovate/gopter" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have used one data generator in our code base already, https://github.com/jaswdr/faker. Is gopter more useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is gopter more useful?
100 times yes -- since it's not just a data generator. Back when we've introduced faker, you might remember our conversations (also with @phiggins, https://github.com/chef/a2/pull/4294 and previous gopter usage introduced earlier: https://github.com/chef/a2/pull/4369): fake data is nice for tests, but the icing on the cake is property based tests. This is what gopter gives us: a way to state properties, and assert them using a large battery of random inputs.
I.e. instead of
generate one random policy, see that it can be created
we formulate a property
when CreateRule is called with some input, it will lead to a rule in storage
and have the framework try that a hundred times with different inputs.
Also, any decent PBT framework gives you shrinking: when it finds an input such that the property not holds, it attempts to find the smallest input that does the same thing. It's vital for dealing with random inputs.
Now, this doesn't mean what we don't need unit tests. And it also doesn't mean that the way we're using it here is the one and only correct way.
fe6a7d9
to
9b87569
Compare
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
This isn't new, it just hadn't been mentioned before. While we're at it, I've selected the latest version, 0.2.4. Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
It's also a property that doesn't hold -- the ProjectRule type limits the condition types. This needs to be encoded in the generators, too. Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Felt better not to pile this onto the existing Policies service. Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
This is so tedious. It's almost, but not exactly, the same code as that written in authz-service, translating from its GRPC service to its internal storage interface (and back). One thing to note: we're using an extra ENUM value in our protobuf definitions in the gateway API. It's a way to ensure that the enum is explicitly provided: if it's left off, the request message with not have the type that happens to be the first enum name, but an "unset" enum that allows discrimination. (Wrapping the enum could help, too, but this is neat and tidy and straightforward.) Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
Signed-off-by: Stephan Renatus <[email protected]>
9b87569
to
429afec
Compare
🔩 Description
Adds the CreateRule GRPC method to
authz-service
, wrapping its storage'sCreateRule
.Items on Note
rules_property_test.go
, where we encode what we want the system to do, and what "random inputs" look like. Then, the gopter library takes care of generating 100 inputs, and asserting the property. If it fails to hold, the library will attempt to shrink the input into a manageable test case (as anything random can be unwieldy).👍 Definition of Done
chef-automate dev grpcurl authz-service
authz-service
method is exposed viaautomate-gateway
👟 Demo Script / Repro Steps
start_all_services
rebuild components/authz-service
,rebuild components/automate-gateway
hab pkg install -b core/jq-static
forjq
)⛓️ Related Resources
✅ Checklist