github.com/camronlevanger/libcompose@v0.4.1-0.20180423130544-6bb86d53fa21/config/validation_test.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  )
    11  
    12  func testValidSchemaV1(t *testing.T, serviceMap RawServiceMap) {
    13  	testValidSchema(t, serviceMap, validate, validateServiceConstraints)
    14  }
    15  
    16  func testValidSchemaV2(t *testing.T, serviceMap RawServiceMap) {
    17  	testValidSchema(t, serviceMap, validateV2, nil)
    18  }
    19  
    20  func testValidSchemaAll(t *testing.T, serviceMap RawServiceMap) {
    21  	testValidSchema(t, serviceMap, validate, validateServiceConstraints)
    22  	testValidSchema(t, serviceMap, validateV2, nil)
    23  }
    24  
    25  func testValidSchema(t *testing.T, serviceMap RawServiceMap, validate func(RawServiceMap) error, validateServiceConstraints func(RawService, string) error) {
    26  	err := validate(serviceMap)
    27  	assert.Nil(t, err)
    28  
    29  	if validateServiceConstraints != nil {
    30  		for name, service := range serviceMap {
    31  			err := validateServiceConstraints(service, name)
    32  			assert.Nil(t, err)
    33  		}
    34  	}
    35  }
    36  
    37  func testInvalidSchemaV1(t *testing.T, serviceMap RawServiceMap, errMsgs []string, errCount int) {
    38  	testInvalidSchema(t, serviceMap, errMsgs, errCount, validate, validateServiceConstraints)
    39  }
    40  
    41  func testInvalidSchemaV2(t *testing.T, serviceMap RawServiceMap, errMsgs []string, errCount int) {
    42  	testInvalidSchema(t, serviceMap, errMsgs, errCount, validateV2, nil)
    43  }
    44  
    45  func testInvalidSchemaAll(t *testing.T, serviceMap RawServiceMap, errMsgs []string, errCount int) {
    46  	testInvalidSchema(t, serviceMap, errMsgs, errCount, validate, validateServiceConstraints)
    47  	testInvalidSchema(t, serviceMap, errMsgs, errCount, validateV2, nil)
    48  }
    49  
    50  func testInvalidSchema(t *testing.T, serviceMap RawServiceMap, errMsgs []string, errCount int, validate func(RawServiceMap) error, validateServiceConstraints func(RawService, string) error) {
    51  	var combinedErrMsg bytes.Buffer
    52  
    53  	err := validate(serviceMap)
    54  	if err != nil {
    55  		combinedErrMsg.WriteString(err.Error())
    56  		combinedErrMsg.WriteRune('\n')
    57  	}
    58  
    59  	if validateServiceConstraints != nil {
    60  		for name, service := range serviceMap {
    61  			err := validateServiceConstraints(service, name)
    62  			if err != nil {
    63  				combinedErrMsg.WriteString(err.Error())
    64  				combinedErrMsg.WriteRune('\n')
    65  			}
    66  		}
    67  	}
    68  
    69  	for _, errMsg := range errMsgs {
    70  		assert.True(t, strings.Contains(combinedErrMsg.String(), errMsg))
    71  	}
    72  
    73  	// gojsonschema has bugs that can cause extraneous errors
    74  	// This makes sure we don't have more errors than expected
    75  	assert.True(t, strings.Count(combinedErrMsg.String(), "\n") == errCount)
    76  }
    77  
    78  func TestInvalidServiceNames(t *testing.T) {
    79  	invalidServiceNames := []string{"?not?allowed", " ", "", "!", "/"}
    80  
    81  	for _, invalidServiceName := range invalidServiceNames {
    82  		testInvalidSchemaAll(t, RawServiceMap{
    83  			invalidServiceName: map[string]interface{}{
    84  				"image": "busybox",
    85  			},
    86  		}, []string{fmt.Sprintf("Invalid service name '%s' - only [a-zA-Z0-9\\._\\-] characters are allowed", invalidServiceName)}, 1)
    87  	}
    88  }
    89  
    90  func TestValidServiceNames(t *testing.T) {
    91  	validServiceNames := []string{"_", "-", ".__.", "_what-up.", "what_.up----", "whatup"}
    92  
    93  	for _, validServiceName := range validServiceNames {
    94  		testValidSchemaAll(t, RawServiceMap{
    95  			validServiceName: map[string]interface{}{
    96  				"image": "busybox",
    97  			},
    98  		})
    99  	}
   100  }
   101  
   102  func TestConfigInvalidPorts(t *testing.T) {
   103  	portsValues := []interface{}{
   104  		map[string]interface{}{
   105  			"1": "8000",
   106  		},
   107  		false,
   108  		0,
   109  		"8000",
   110  	}
   111  
   112  	for _, portsValue := range portsValues {
   113  		testInvalidSchemaAll(t, RawServiceMap{
   114  			"web": map[string]interface{}{
   115  				"image": "busybox",
   116  				"ports": portsValue,
   117  			},
   118  		}, []string{"Service 'web' configuration key 'ports' contains an invalid type, it should be an array"}, 1)
   119  	}
   120  
   121  	testInvalidSchemaAll(t, RawServiceMap{
   122  		"web": map[string]interface{}{
   123  			"image": "busybox",
   124  			"ports": []interface{}{
   125  				"8000",
   126  				"8000",
   127  			},
   128  		},
   129  	}, []string{"Service 'web' configuration key 'ports' value [8000 8000] has non-unique elements"}, 1)
   130  }
   131  
   132  func TestConfigValidPorts(t *testing.T) {
   133  	portsValues := []interface{}{
   134  		[]interface{}{"8000", "9000"},
   135  		[]interface{}{"8000"},
   136  		[]interface{}{8000},
   137  		[]interface{}{"127.0.0.1::8000"},
   138  		[]interface{}{"49153-49154:3002-3003"},
   139  	}
   140  
   141  	for _, portsValue := range portsValues {
   142  		testValidSchemaAll(t, RawServiceMap{
   143  			"web": map[string]interface{}{
   144  				"image": "busybox",
   145  				"ports": portsValue,
   146  			},
   147  		})
   148  	}
   149  }
   150  
   151  func TestConfigHint(t *testing.T) {
   152  	testInvalidSchemaAll(t, RawServiceMap{
   153  		"foo": map[string]interface{}{
   154  			"image":     "busybox",
   155  			"privilege": "something",
   156  		},
   157  	}, []string{"Unsupported config option for foo service: 'privilege' (did you mean 'privileged'?)"}, 1)
   158  }
   159  
   160  func TestTypeShouldBeAnArray(t *testing.T) {
   161  	testInvalidSchemaAll(t, RawServiceMap{
   162  		"foo": map[string]interface{}{
   163  			"image": "busybox",
   164  			"links": "an_link",
   165  		},
   166  	}, []string{"Service 'foo' configuration key 'links' contains an invalid type, it should be an array"}, 1)
   167  }
   168  
   169  func TestInvalidTypeWithMultipleValidTypes(t *testing.T) {
   170  	testInvalidSchemaAll(t, RawServiceMap{
   171  		"web": map[string]interface{}{
   172  			"image": "busybox",
   173  			"mem_limit": []interface{}{
   174  				"array_elem",
   175  			},
   176  		},
   177  	}, []string{"Service 'web' configuration key 'mem_limit' contains an invalid type, it should be a number or string."}, 1)
   178  }
   179  
   180  func TestInvalidNotUniqueItems(t *testing.T) {
   181  	// Test property with array as only valid type
   182  	testInvalidSchemaAll(t, RawServiceMap{
   183  		"foo": map[string]interface{}{
   184  			"image": "busybox",
   185  			"devices": []string{
   186  				"/dev/foo:/dev/foo",
   187  				"/dev/foo:/dev/foo",
   188  			},
   189  		},
   190  	}, []string{"Service 'foo' configuration key 'devices' value [/dev/foo:/dev/foo /dev/foo:/dev/foo] has non-unique elements"}, 1)
   191  
   192  	// Test property with multiple valid types
   193  	testInvalidSchemaAll(t, RawServiceMap{
   194  		"foo": map[string]interface{}{
   195  			"image": "busybox",
   196  			"environment": []string{
   197  				"KEY=VAL",
   198  				"KEY=VAL",
   199  			},
   200  		},
   201  	}, []string{"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]"}, 1)
   202  }
   203  
   204  func TestInvalidListOfStringsFormat(t *testing.T) {
   205  	testInvalidSchemaAll(t, RawServiceMap{
   206  		"web": map[string]interface{}{
   207  			"build": ".",
   208  			"command": []interface{}{
   209  				1,
   210  			},
   211  		},
   212  	}, []string{"Service 'web' configuration key 'command' contains 1, which is an invalid type, it should be a string"}, 1)
   213  }
   214  
   215  func TestInvalidExtraHostsString(t *testing.T) {
   216  	testInvalidSchemaAll(t, RawServiceMap{
   217  		"web": map[string]interface{}{
   218  			"image":       "busybox",
   219  			"extra_hosts": "somehost:162.242.195.82",
   220  		},
   221  	}, []string{"Service 'web' configuration key 'extra_hosts' contains an invalid type, it should be an array or object"}, 1)
   222  }
   223  
   224  func TestValidConfigWhichAllowsTwoTypeDefinitions(t *testing.T) {
   225  	for _, exposeValue := range []interface{}{"8000", 9000} {
   226  		testValidSchemaAll(t, RawServiceMap{
   227  			"web": map[string]interface{}{
   228  				"image": "busybox",
   229  				"expose": []interface{}{
   230  					exposeValue,
   231  				},
   232  			},
   233  		})
   234  	}
   235  }
   236  
   237  func TestValidConfigOneOfStringOrList(t *testing.T) {
   238  	entrypointValues := []interface{}{
   239  		[]interface{}{
   240  			"sh",
   241  		},
   242  		"sh",
   243  	}
   244  
   245  	for _, entrypointValue := range entrypointValues {
   246  		testValidSchemaAll(t, RawServiceMap{
   247  			"web": map[string]interface{}{
   248  				"image":      "busybox",
   249  				"entrypoint": entrypointValue,
   250  			},
   251  		})
   252  	}
   253  }
   254  
   255  func TestInvalidServiceProperty(t *testing.T) {
   256  	testInvalidSchemaAll(t, RawServiceMap{
   257  		"web": map[string]interface{}{
   258  			"image":            "busybox",
   259  			"invalid_property": "value",
   260  		},
   261  	}, []string{"Unsupported config option for web service: 'invalid_property'"}, 1)
   262  }
   263  
   264  func TestServiceInvalidMissingImageAndBuild(t *testing.T) {
   265  	testInvalidSchemaV1(t, RawServiceMap{
   266  		"web": map[string]interface{}{},
   267  	}, []string{"Service 'web' has neither an image nor a build path specified. Exactly one must be provided."}, 1)
   268  }
   269  
   270  func TestServiceInvalidSpecifiesImageAndBuild(t *testing.T) {
   271  	testInvalidSchemaV1(t, RawServiceMap{
   272  		"web": map[string]interface{}{
   273  			"image": "busybox",
   274  			"build": ".",
   275  		},
   276  	}, []string{"Service 'web' has both an image and build path specified. A service can either be built to image or use an existing image, not both."}, 1)
   277  }
   278  
   279  func TestServiceInvalidSpecifiesImageAndDockerfile(t *testing.T) {
   280  	testInvalidSchemaV1(t, RawServiceMap{
   281  		"web": map[string]interface{}{
   282  			"image":      "busybox",
   283  			"dockerfile": "Dockerfile",
   284  		},
   285  	}, []string{"Service 'web' has both an image and alternate Dockerfile. A service can either be built to image or use an existing image, not both."}, 1)
   286  }
   287  
   288  func TestInvalidServiceForMultipleErrors(t *testing.T) {
   289  	testInvalidSchemaAll(t, RawServiceMap{
   290  		"foo": map[string]interface{}{
   291  			"image": "busybox",
   292  			"ports": "invalid_type",
   293  			"links": "an_type",
   294  			"environment": []string{
   295  				"KEY=VAL",
   296  				"KEY=VAL",
   297  			},
   298  		},
   299  	}, []string{
   300  		"Service 'foo' configuration key 'ports' contains an invalid type, it should be an array",
   301  		"Service 'foo' configuration key 'links' contains an invalid type, it should be an array",
   302  		"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
   303  	}, 3)
   304  }
   305  
   306  func TestInvalidServiceWithAdditionalProperties(t *testing.T) {
   307  	testInvalidSchemaAll(t, RawServiceMap{
   308  		"foo": map[string]interface{}{
   309  			"image": "busybox",
   310  			"ports": "invalid_type",
   311  			"---":   "nope",
   312  			"environment": []string{
   313  				"KEY=VAL",
   314  				"KEY=VAL",
   315  			},
   316  		},
   317  	}, []string{
   318  		"Service 'foo' configuration key 'ports' contains an invalid type, it should be an array",
   319  		"Unsupported config option for foo service: '---'",
   320  		"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
   321  	}, 3)
   322  }
   323  
   324  func TestMultipleInvalidServices(t *testing.T) {
   325  	testInvalidSchemaAll(t, RawServiceMap{
   326  		"foo1": map[string]interface{}{
   327  			"image": "busybox",
   328  			"ports": "invalid_type",
   329  		},
   330  		"foo2": map[string]interface{}{
   331  			"image": "busybox",
   332  			"ports": "invalid_type",
   333  		},
   334  	}, []string{
   335  		"Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
   336  		"Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
   337  	}, 2)
   338  }
   339  
   340  func TestMixedInvalidServicesAndInvalidServiceNames(t *testing.T) {
   341  	testInvalidSchemaAll(t, RawServiceMap{
   342  		"foo1": map[string]interface{}{
   343  			"image": "busybox",
   344  			"ports": "invalid_type",
   345  		},
   346  		"???": map[string]interface{}{
   347  			"image": "busybox",
   348  		},
   349  		"foo2": map[string]interface{}{
   350  			"image": "busybox",
   351  			"ports": "invalid_type",
   352  		},
   353  	}, []string{
   354  		"Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
   355  		"Invalid service name '???' - only [a-zA-Z0-9\\._\\-] characters are allowed",
   356  		"Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
   357  	}, 3)
   358  }
   359  
   360  func TestMultipleInvalidServicesForMultipleErrors(t *testing.T) {
   361  	testInvalidSchemaAll(t, RawServiceMap{
   362  		"foo1": map[string]interface{}{
   363  			"image": "busybox",
   364  			"ports": "invalid_type",
   365  			"environment": []string{
   366  				"KEY=VAL",
   367  				"KEY=VAL",
   368  			},
   369  		},
   370  		"foo2": map[string]interface{}{
   371  			"image": "busybox",
   372  			"ports": "invalid_type",
   373  			"environment": []string{
   374  				"KEY=VAL",
   375  				"KEY=VAL",
   376  			},
   377  		},
   378  	}, []string{
   379  		"Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
   380  		"Service 'foo1' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
   381  		"Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
   382  		"Service 'foo2' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
   383  	}, 4)
   384  }