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 }