package structs
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestServiceResolverConfigEntry ( t * testing . T ) {
t . Parallel ( )
for _ , tc := range [ ] struct {
name string
entry * ServiceResolverConfigEntry
normalizeErr string
validateErr string
// check is called between normalize and validate
check func ( t * testing . T , entry * ServiceResolverConfigEntry )
} {
{
name : "nil" ,
entry : nil ,
normalizeErr : "config entry is nil" ,
} ,
{
name : "no name" ,
entry : & ServiceResolverConfigEntry { } ,
validateErr : "Name is required" ,
} ,
{
name : "empty" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
} ,
} ,
{
name : "empty subset name" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Subsets : map [ string ] ServiceResolverSubset {
"" : { OnlyPassing : true } ,
} ,
} ,
validateErr : "Subset defined with empty name" ,
} ,
{
name : "default subset does not exist" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
DefaultSubset : "gone" ,
Subsets : map [ string ] ServiceResolverSubset {
"v1" : { Filter : "Service.Meta.version == v1" } ,
} ,
} ,
validateErr : ` DefaultSubset "gone" is not a valid subset ` ,
} ,
{
name : "default subset does exist" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
DefaultSubset : "v1" ,
Subsets : map [ string ] ServiceResolverSubset {
"v1" : { Filter : "Service.Meta.version == v1" } ,
} ,
} ,
} ,
{
name : "empty redirect" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Redirect : & ServiceResolverRedirect { } ,
} ,
validateErr : "Redirect is empty" ,
} ,
{
name : "redirect subset with no service" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Redirect : & ServiceResolverRedirect {
ServiceSubset : "next" ,
} ,
} ,
validateErr : "Redirect.ServiceSubset defined without Redirect.Service" ,
} ,
{
name : "redirect namespace with no service" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Redirect : & ServiceResolverRedirect {
Namespace : "alternate" ,
} ,
} ,
validateErr : "Redirect.Namespace defined without Redirect.Service" ,
} ,
{
name : "self redirect with invalid subset" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Redirect : & ServiceResolverRedirect {
Service : "test" ,
ServiceSubset : "gone" ,
} ,
} ,
validateErr : ` Redirect.ServiceSubset "gone" is not a valid subset of "test" ` ,
} ,
{
name : "self redirect with valid subset" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Redirect : & ServiceResolverRedirect {
Service : "test" ,
ServiceSubset : "v1" ,
} ,
Subsets : map [ string ] ServiceResolverSubset {
"v1" : { Filter : "Service.Meta.version == v1" } ,
} ,
} ,
} ,
{
name : "simple wildcard failover" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Failover : map [ string ] ServiceResolverFailover {
"*" : ServiceResolverFailover {
Datacenters : [ ] string { "dc2" } ,
} ,
} ,
} ,
} ,
{
name : "failover for missing subset" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Failover : map [ string ] ServiceResolverFailover {
"gone" : ServiceResolverFailover {
Datacenters : [ ] string { "dc2" } ,
} ,
} ,
} ,
validateErr : ` Bad Failover["gone"]: not a valid subset ` ,
} ,
{
name : "failover for present subset" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Subsets : map [ string ] ServiceResolverSubset {
"v1" : { Filter : "Service.Meta.version == v1" } ,
} ,
Failover : map [ string ] ServiceResolverFailover {
"v1" : ServiceResolverFailover {
Datacenters : [ ] string { "dc2" } ,
} ,
} ,
} ,
} ,
{
name : "failover empty" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Subsets : map [ string ] ServiceResolverSubset {
"v1" : { Filter : "Service.Meta.version == v1" } ,
} ,
Failover : map [ string ] ServiceResolverFailover {
"v1" : ServiceResolverFailover { } ,
} ,
} ,
validateErr : ` Bad Failover["v1"] one of Service, ServiceSubset, Namespace, or Datacenters is required ` ,
} ,
{
name : "failover to self using invalid subset" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Subsets : map [ string ] ServiceResolverSubset {
"v1" : { Filter : "Service.Meta.version == v1" } ,
} ,
Failover : map [ string ] ServiceResolverFailover {
"v1" : ServiceResolverFailover {
Service : "test" ,
ServiceSubset : "gone" ,
} ,
} ,
} ,
validateErr : ` Bad Failover["v1"].ServiceSubset "gone" is not a valid subset of "test" ` ,
} ,
{
name : "failover to self using valid subset" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Subsets : map [ string ] ServiceResolverSubset {
"v1" : { Filter : "Service.Meta.version == v1" } ,
"v2" : { Filter : "Service.Meta.version == v2" } ,
} ,
Failover : map [ string ] ServiceResolverFailover {
"v1" : ServiceResolverFailover {
Service : "test" ,
ServiceSubset : "v2" ,
} ,
} ,
} ,
} ,
{
name : "failover with invalid overprovisioning factor" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Failover : map [ string ] ServiceResolverFailover {
"*" : ServiceResolverFailover {
Service : "backup" ,
OverprovisioningFactor : - 1 ,
} ,
} ,
} ,
validateErr : ` Bad Failover["*"].OverprovisioningFactor '-1', must be >= 0 ` ,
} ,
{
name : "failover with empty datacenters in list" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
Failover : map [ string ] ServiceResolverFailover {
"*" : ServiceResolverFailover {
Service : "backup" ,
Datacenters : [ ] string { "" , "dc2" , "dc3" } ,
} ,
} ,
} ,
validateErr : ` Bad Failover["*"].Datacenters: found empty datacenter ` ,
} ,
{
name : "bad connect timeout" ,
entry : & ServiceResolverConfigEntry {
Kind : ServiceResolver ,
Name : "test" ,
ConnectTimeout : - 1 * time . Second ,
} ,
validateErr : "Bad ConnectTimeout" ,
} ,
} {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
err := tc . entry . Normalize ( )
if tc . normalizeErr != "" {
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , tc . normalizeErr )
return
}
require . NoError ( t , err )
if tc . check != nil {
tc . check ( t , tc . entry )
}
err = tc . entry . Validate ( )
if tc . validateErr != "" {
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , tc . validateErr )
return
}
require . NoError ( t , err )
} )
}
}
func TestServiceSplitterConfigEntry ( t * testing . T ) {
t . Parallel ( )
makesplitter := func ( splits ... ServiceSplit ) * ServiceSplitterConfigEntry {
return & ServiceSplitterConfigEntry {
Kind : ServiceSplitter ,
Name : "test" ,
Splits : splits ,
}
}
makesplit := func ( weight float32 , service , serviceSubset , namespace string ) ServiceSplit {
return ServiceSplit {
Weight : weight ,
Service : service ,
ServiceSubset : serviceSubset ,
Namespace : namespace ,
}
}
for _ , tc := range [ ] struct {
name string
entry * ServiceSplitterConfigEntry
normalizeErr string
validateErr string
// check is called between normalize and validate
check func ( t * testing . T , entry * ServiceSplitterConfigEntry )
} {
{
name : "nil" ,
entry : nil ,
normalizeErr : "config entry is nil" ,
} ,
{
name : "no name" ,
entry : & ServiceSplitterConfigEntry { } ,
validateErr : "Name is required" ,
} ,
{
name : "empty" ,
entry : makesplitter ( ) ,
validateErr : "no splits configured" ,
} ,
{
name : "1 split" ,
entry : makesplitter (
makesplit ( 100 , "test" , "" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 100 ) , entry . Splits [ 0 ] . Weight )
} ,
} ,
{
name : "1 split not enough weight" ,
entry : makesplitter (
makesplit ( 99.99 , "test" , "" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 99.99 ) , entry . Splits [ 0 ] . Weight )
} ,
validateErr : "the sum of all split weights must be 100" ,
} ,
{
name : "1 split too much weight" ,
entry : makesplitter (
makesplit ( 100.01 , "test" , "" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 100.01 ) , entry . Splits [ 0 ] . Weight )
} ,
validateErr : "the sum of all split weights must be 100" ,
} ,
{
name : "2 splits" ,
entry : makesplitter (
makesplit ( 99 , "test" , "v1" , "" ) ,
makesplit ( 1 , "test" , "v2" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 99 ) , entry . Splits [ 0 ] . Weight )
require . Equal ( t , float32 ( 1 ) , entry . Splits [ 1 ] . Weight )
} ,
} ,
{
name : "2 splits - rounded up to smallest units" ,
entry : makesplitter (
makesplit ( 99.999 , "test" , "v1" , "" ) ,
makesplit ( 0.001 , "test" , "v2" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 100 ) , entry . Splits [ 0 ] . Weight )
require . Equal ( t , float32 ( 0 ) , entry . Splits [ 1 ] . Weight )
} ,
} ,
{
name : "2 splits not enough weight" ,
entry : makesplitter (
makesplit ( 99.98 , "test" , "v1" , "" ) ,
makesplit ( 0.01 , "test" , "v2" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 99.98 ) , entry . Splits [ 0 ] . Weight )
require . Equal ( t , float32 ( 0.01 ) , entry . Splits [ 1 ] . Weight )
} ,
validateErr : "the sum of all split weights must be 100" ,
} ,
{
name : "2 splits too much weight" ,
entry : makesplitter (
makesplit ( 100 , "test" , "v1" , "" ) ,
makesplit ( 0.01 , "test" , "v2" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 100 ) , entry . Splits [ 0 ] . Weight )
require . Equal ( t , float32 ( 0.01 ) , entry . Splits [ 1 ] . Weight )
} ,
validateErr : "the sum of all split weights must be 100" ,
} ,
{
name : "3 splits" ,
entry : makesplitter (
makesplit ( 34 , "test" , "v1" , "" ) ,
makesplit ( 33 , "test" , "v2" , "" ) ,
makesplit ( 33 , "test" , "v3" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 34 ) , entry . Splits [ 0 ] . Weight )
require . Equal ( t , float32 ( 33 ) , entry . Splits [ 1 ] . Weight )
require . Equal ( t , float32 ( 33 ) , entry . Splits [ 2 ] . Weight )
} ,
} ,
{
name : "3 splits one duplicated same weights" ,
entry : makesplitter (
makesplit ( 34 , "test" , "v1" , "" ) ,
makesplit ( 33 , "test" , "v2" , "" ) ,
makesplit ( 33 , "test" , "v2" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 34 ) , entry . Splits [ 0 ] . Weight )
require . Equal ( t , float32 ( 33 ) , entry . Splits [ 1 ] . Weight )
require . Equal ( t , float32 ( 33 ) , entry . Splits [ 2 ] . Weight )
} ,
validateErr : "split destination occurs more than once" ,
} ,
{
name : "3 splits one duplicated diff weights" ,
entry : makesplitter (
makesplit ( 34 , "test" , "v1" , "" ) ,
makesplit ( 33 , "test" , "v2" , "" ) ,
makesplit ( 33 , "test" , "v1" , "" ) ,
) ,
check : func ( t * testing . T , entry * ServiceSplitterConfigEntry ) {
require . Equal ( t , float32 ( 34 ) , entry . Splits [ 0 ] . Weight )
require . Equal ( t , float32 ( 33 ) , entry . Splits [ 1 ] . Weight )
require . Equal ( t , float32 ( 33 ) , entry . Splits [ 2 ] . Weight )
} ,
validateErr : "split destination occurs more than once" ,
} ,
} {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
err := tc . entry . Normalize ( )
if tc . normalizeErr != "" {
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , tc . normalizeErr )
return
}
require . NoError ( t , err )
if tc . check != nil {
tc . check ( t , tc . entry )
}
err = tc . entry . Validate ( )
if tc . validateErr != "" {
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , tc . validateErr )
return
}
require . NoError ( t , err )
} )
}
}