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 }