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