github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/compose/schema/schema_test.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "testing" 6 7 "gotest.tools/v3/assert" 8 ) 9 10 type dict map[string]interface{} 11 12 func TestValidate(t *testing.T) { 13 config := dict{ 14 "version": "3.0", 15 "services": dict{ 16 "foo": dict{ 17 "image": "busybox", 18 }, 19 }, 20 } 21 22 assert.NilError(t, Validate(config, "3.0")) 23 assert.NilError(t, Validate(config, "3")) 24 assert.NilError(t, Validate(config, "")) 25 assert.ErrorContains(t, Validate(config, "1.0"), "unsupported Compose file version: 1.0") 26 assert.ErrorContains(t, Validate(config, "12345"), "unsupported Compose file version: 12345") 27 } 28 29 func TestValidateUndefinedTopLevelOption(t *testing.T) { 30 config := dict{ 31 "version": "3.0", 32 "helicopters": dict{ 33 "foo": dict{ 34 "image": "busybox", 35 }, 36 }, 37 } 38 39 err := Validate(config, "3.0") 40 assert.ErrorContains(t, err, "Additional property helicopters is not allowed") 41 } 42 43 func TestValidateAllowsXTopLevelFields(t *testing.T) { 44 config := dict{ 45 "version": "3.4", 46 "x-extra-stuff": dict{}, 47 } 48 49 err := Validate(config, "3.4") 50 assert.NilError(t, err) 51 } 52 53 func TestValidateAllowsXFields(t *testing.T) { 54 config := dict{ 55 "version": "3.7", 56 "services": dict{ 57 "bar": dict{ 58 "x-extra-stuff": dict{}, 59 }, 60 }, 61 "volumes": dict{ 62 "bar": dict{ 63 "x-extra-stuff": dict{}, 64 }, 65 }, 66 "networks": dict{ 67 "bar": dict{ 68 "x-extra-stuff": dict{}, 69 }, 70 }, 71 "configs": dict{ 72 "bar": dict{ 73 "x-extra-stuff": dict{}, 74 }, 75 }, 76 "secrets": dict{ 77 "bar": dict{ 78 "x-extra-stuff": dict{}, 79 }, 80 }, 81 } 82 err := Validate(config, "3.7") 83 assert.NilError(t, err) 84 } 85 86 func TestValidateCredentialSpecs(t *testing.T) { 87 tests := []struct { 88 version string 89 expectedErr string 90 }{ 91 {version: "3.0", expectedErr: "credential_spec"}, 92 {version: "3.1", expectedErr: "credential_spec"}, 93 {version: "3.2", expectedErr: "credential_spec"}, 94 {version: "3.3", expectedErr: "config"}, 95 {version: "3.4", expectedErr: "config"}, 96 {version: "3.5", expectedErr: "config"}, 97 {version: "3.6", expectedErr: "config"}, 98 {version: "3.7", expectedErr: "config"}, 99 {version: "3.8"}, 100 {version: "3.9"}, 101 {version: "3.10"}, 102 {version: "3"}, 103 {version: ""}, 104 } 105 106 for _, tc := range tests { 107 tc := tc 108 t.Run(tc.version, func(t *testing.T) { 109 config := dict{ 110 "version": "99.99", 111 "services": dict{ 112 "foo": dict{ 113 "image": "busybox", 114 "credential_spec": dict{ 115 "config": "foobar", 116 }, 117 }, 118 }, 119 } 120 err := Validate(config, tc.version) 121 if tc.expectedErr != "" { 122 assert.ErrorContains(t, err, fmt.Sprintf("Additional property %s is not allowed", tc.expectedErr)) 123 } else { 124 assert.NilError(t, err) 125 } 126 }) 127 } 128 } 129 130 func TestValidateSecretConfigNames(t *testing.T) { 131 config := dict{ 132 "version": "3.5", 133 "configs": dict{ 134 "bar": dict{ 135 "name": "foobar", 136 }, 137 }, 138 "secrets": dict{ 139 "baz": dict{ 140 "name": "foobaz", 141 }, 142 }, 143 } 144 145 err := Validate(config, "3.5") 146 assert.NilError(t, err) 147 } 148 149 func TestValidateInvalidVersion(t *testing.T) { 150 config := dict{ 151 "version": "2.1", 152 "services": dict{ 153 "foo": dict{ 154 "image": "busybox", 155 }, 156 }, 157 } 158 159 err := Validate(config, "2.1") 160 assert.ErrorContains(t, err, "unsupported Compose file version: 2.1") 161 } 162 163 type array []interface{} 164 165 func TestValidatePlacement(t *testing.T) { 166 config := dict{ 167 "version": "3.3", 168 "services": dict{ 169 "foo": dict{ 170 "image": "busybox", 171 "deploy": dict{ 172 "placement": dict{ 173 "preferences": array{ 174 dict{ 175 "spread": "node.labels.az", 176 }, 177 }, 178 }, 179 }, 180 }, 181 }, 182 } 183 184 assert.NilError(t, Validate(config, "3.3")) 185 } 186 187 func TestValidateIsolation(t *testing.T) { 188 config := dict{ 189 "version": "3.5", 190 "services": dict{ 191 "foo": dict{ 192 "image": "busybox", 193 "isolation": "some-isolation-value", 194 }, 195 }, 196 } 197 assert.NilError(t, Validate(config, "3.5")) 198 } 199 200 func TestValidateRollbackConfig(t *testing.T) { 201 config := dict{ 202 "version": "3.4", 203 "services": dict{ 204 "foo": dict{ 205 "image": "busybox", 206 "deploy": dict{ 207 "rollback_config": dict{ 208 "parallelism": 1, 209 }, 210 }, 211 }, 212 }, 213 } 214 215 assert.NilError(t, Validate(config, "3.7")) 216 } 217 218 func TestValidateRollbackConfigWithOrder(t *testing.T) { 219 config := dict{ 220 "version": "3.4", 221 "services": dict{ 222 "foo": dict{ 223 "image": "busybox", 224 "deploy": dict{ 225 "rollback_config": dict{ 226 "parallelism": 1, 227 "order": "start-first", 228 }, 229 }, 230 }, 231 }, 232 } 233 234 assert.NilError(t, Validate(config, "3.7")) 235 } 236 237 func TestValidateRollbackConfigWithUpdateConfig(t *testing.T) { 238 config := dict{ 239 "version": "3.4", 240 "services": dict{ 241 "foo": dict{ 242 "image": "busybox", 243 "deploy": dict{ 244 "update_config": dict{ 245 "parallelism": 1, 246 "order": "start-first", 247 }, 248 "rollback_config": dict{ 249 "parallelism": 1, 250 "order": "start-first", 251 }, 252 }, 253 }, 254 }, 255 } 256 257 assert.NilError(t, Validate(config, "3.7")) 258 } 259 260 func TestValidateRollbackConfigWithUpdateConfigFull(t *testing.T) { 261 config := dict{ 262 "version": "3.4", 263 "services": dict{ 264 "foo": dict{ 265 "image": "busybox", 266 "deploy": dict{ 267 "update_config": dict{ 268 "parallelism": 1, 269 "order": "start-first", 270 "delay": "10s", 271 "failure_action": "pause", 272 "monitor": "10s", 273 }, 274 "rollback_config": dict{ 275 "parallelism": 1, 276 "order": "start-first", 277 "delay": "10s", 278 "failure_action": "pause", 279 "monitor": "10s", 280 }, 281 }, 282 }, 283 }, 284 } 285 286 assert.NilError(t, Validate(config, "3.7")) 287 }