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