Compare commits

...

2 Commits

Author SHA1 Message Date
Adrien Marquès 9c3166397f
add tests for service collision
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2020-03-21 15:49:07 +01:00
Adrien Marquès e3adbf48ca
consider collision only if every part is matching 2020-03-21 15:48:59 +01:00
2 changed files with 241 additions and 8 deletions

View File

@ -559,6 +559,212 @@ func TestParseParameters(t *testing.T) {
} }
func TestServiceCollision(t *testing.T) {
t.Parallel()
tests := []struct {
Config string
Error error
}{
{
`[
{ "method": "GET", "path": "/a",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/b",
"info": "info", "in": {}
}
]`,
nil,
},
{
`[
{ "method": "GET", "path": "/a",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a",
"info": "info", "in": {}
}
]`,
ErrPatternCollision,
},
{
`[
{ "method": "GET", "path": "/a",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/b",
"info": "info", "in": {}
}
]`,
nil,
},
{
`[
{ "method": "GET", "path": "/a/b",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a",
"info": "info", "in": {}
}
]`,
nil,
},
{
`[
{ "method": "GET", "path": "/a/b",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}",
"info": "info", "in": {
"{c}": { "info":"info", "type": "string" }
}
}
]`,
ErrPatternCollision,
},
{
`[
{ "method": "GET", "path": "/a/b",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}",
"info": "info", "in": {
"{c}": { "info":"info", "type": "uint" }
}
}
]`,
nil,
},
{
`[
{ "method": "GET", "path": "/a/b/d",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}/d",
"info": "info", "in": {
"{c}": { "info":"info", "type": "string" }
}
}
]`,
ErrPatternCollision,
},
{
`[
{ "method": "GET", "path": "/a/123",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}",
"info": "info", "in": {
"{c}": { "info":"info", "type": "string" }
}
}
]`,
ErrPatternCollision,
},
{
`[
{ "method": "GET", "path": "/a/123/d",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}",
"info": "info", "in": {
"{c}": { "info":"info", "type": "string" }
}
}
]`,
nil,
},
{
`[
{ "method": "GET", "path": "/a/123",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}/d",
"info": "info", "in": {
"{c}": { "info":"info", "type": "string" }
}
}
]`,
nil,
},
{
`[
{ "method": "GET", "path": "/a/123",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}",
"info": "info", "in": {
"{c}": { "info":"info", "type": "uint" }
}
}
]`,
ErrPatternCollision,
},
{
`[
{ "method": "GET", "path": "/a/123/d",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}",
"info": "info", "in": {
"{c}": { "info":"info", "type": "uint" }
}
}
]`,
nil,
},
{
`[
{ "method": "GET", "path": "/a/123",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}/d",
"info": "info", "in": {
"{c}": { "info":"info", "type": "uint" }
}
}
]`,
nil,
},
{
`[
{ "method": "GET", "path": "/a/123/d",
"info": "info", "in": {}
},
{ "method": "GET", "path": "/a/{c}/d",
"info": "info", "in": {
"{c}": { "info":"info", "type": "uint" }
}
}
]`,
ErrPatternCollision,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) {
_, err := Parse(strings.NewReader(test.Config), builtin.StringDataType{}, builtin.UintDataType{})
if err == nil && test.Error != nil {
t.Errorf("expected an error: '%s'", test.Error.Error())
t.FailNow()
}
if err != nil && test.Error == nil {
t.Errorf("unexpected error: '%s'", err.Error())
t.FailNow()
}
if err != nil && test.Error != nil {
if !errors.Is(err, test.Error) {
t.Errorf("expected the error <%s> got <%s>", test.Error, err)
t.FailNow()
}
}
})
}
}
func TestMatchSimple(t *testing.T) { func TestMatchSimple(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {

View File

@ -74,6 +74,8 @@ func (server *Server) collide() error {
continue continue
} }
partErrors := make([]error, 0)
// for each part // for each part
for pi, aPart := range aParts { for pi, aPart := range aParts {
bPart := bParts[pi] bPart := bParts[pi]
@ -83,13 +85,14 @@ func (server *Server) collide() error {
// both captures -> as we cannot check, consider a collision // both captures -> as we cannot check, consider a collision
if aIsCapture && bIsCapture { if aIsCapture && bIsCapture {
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern) partErrors = append(partErrors, fmt.Errorf("%s '%s': %w (path %s and %s)", aService.Method, aService.Pattern, ErrPatternCollision, aPart, bPart))
continue
} }
// no capture -> check equal // no capture -> check equal
if !aIsCapture && !bIsCapture { if !aIsCapture && !bIsCapture {
if aPart == bPart { if aPart == bPart {
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern) partErrors = append(partErrors, fmt.Errorf("%s '%s': %w (same path '%s')", aService.Method, aService.Pattern, ErrPatternCollision, aPart))
} }
continue continue
} }
@ -100,12 +103,14 @@ func (server *Server) collide() error {
// fail if no type or no validator // fail if no type or no validator
if !exists || input.Validator == nil { if !exists || input.Validator == nil {
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern) partErrors = append(partErrors, fmt.Errorf("%s '%s': %w (invalid type for %s)", aService.Method, aService.Pattern, ErrPatternCollision, aPart))
continue
} }
// fail if not valid // fail if not valid
if _, valid := input.Validator(aPart); !valid { if _, valid := input.Validator(bPart); valid {
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern) partErrors = append(partErrors, fmt.Errorf("%s '%s': %w (%s captures '%s')", aService.Method, aService.Pattern, ErrPatternCollision, aPart, bPart))
continue
} }
// B captures A -> check type (A is B ?) // B captures A -> check type (A is B ?)
@ -114,15 +119,37 @@ func (server *Server) collide() error {
// fail if no type or no validator // fail if no type or no validator
if !exists || input.Validator == nil { if !exists || input.Validator == nil {
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern) partErrors = append(partErrors, fmt.Errorf("%s '%s': %w (invalid type for %s)", bService.Method, bService.Pattern, ErrPatternCollision, bPart))
continue
} }
// fail if not valid // fail if not valid
if _, valid := input.Validator(bPart); !valid { if _, valid := input.Validator(aPart); valid {
return fmt.Errorf("%s: %s '%s'", ErrPatternCollision, aService.Method, aService.Pattern) partErrors = append(partErrors, fmt.Errorf("%s '%s': %w (%s captures '%s')", bService.Method, bService.Pattern, ErrPatternCollision, bPart, aPart))
continue
} }
} }
partErrors = append(partErrors, nil)
}
// if at least 1 url part does not match -> ok
var firstError error
oneMismatch := false
for _, err := range partErrors {
if err != nil && firstError == nil {
firstError = err
}
if err == nil {
oneMismatch = true
continue
}
}
if !oneMismatch {
return firstError
} }
} }