github.com/databricks/cli@v0.203.0/bundle/config/interpolation/interpolation_test.go (about) 1 package interpolation 2 3 import ( 4 "testing" 5 6 "github.com/databricks/cli/bundle/config" 7 "github.com/databricks/cli/bundle/config/variable" 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 ) 11 12 type nest struct { 13 X string `json:"x"` 14 Y *string `json:"y"` 15 Z map[string]string `json:"z"` 16 } 17 18 type foo struct { 19 A string `json:"a"` 20 B string `json:"b"` 21 C string `json:"c"` 22 23 // Pointer field 24 D *string `json:"d"` 25 26 // Struct field 27 E nest `json:"e"` 28 29 // Map field 30 F map[string]string `json:"f"` 31 } 32 33 func expand(v any) error { 34 a := accumulator{} 35 a.start(v) 36 return a.expand(DefaultLookup) 37 } 38 39 func TestInterpolationVariables(t *testing.T) { 40 f := foo{ 41 A: "a", 42 B: "${a}", 43 C: "${a}", 44 } 45 46 err := expand(&f) 47 require.NoError(t, err) 48 49 assert.Equal(t, "a", f.A) 50 assert.Equal(t, "a", f.B) 51 assert.Equal(t, "a", f.C) 52 } 53 54 func TestInterpolationVariablesSpecialChars(t *testing.T) { 55 type bar struct { 56 A string `json:"a-b"` 57 B string `json:"b_c"` 58 C string `json:"c-_a"` 59 } 60 f := bar{ 61 A: "a", 62 B: "${a-b}", 63 C: "${a-b}", 64 } 65 66 err := expand(&f) 67 require.NoError(t, err) 68 69 assert.Equal(t, "a", f.A) 70 assert.Equal(t, "a", f.B) 71 assert.Equal(t, "a", f.C) 72 } 73 74 func TestInterpolationValidMatches(t *testing.T) { 75 expectedMatches := map[string]string{ 76 "${hello_world.world_world}": "hello_world.world_world", 77 "${helloworld.world-world}": "helloworld.world-world", 78 "${hello-world.world-world}": "hello-world.world-world", 79 } 80 for interpolationStr, expectedMatch := range expectedMatches { 81 match := re.FindStringSubmatch(interpolationStr) 82 assert.True(t, len(match) > 0, 83 "Failed to match %s and find %s", interpolationStr, expectedMatch) 84 assert.Equal(t, expectedMatch, match[1], 85 "Failed to match the exact pattern %s and find %s", interpolationStr, expectedMatch) 86 } 87 } 88 89 func TestInterpolationInvalidMatches(t *testing.T) { 90 invalidMatches := []string{ 91 "${hello_world-.world_world}", // the first segment ending must not end with hyphen (-) 92 "${hello_world-_.world_world}", // the first segment ending must not end with underscore (_) 93 "${helloworld.world-world-}", // second segment must not end with hyphen (-) 94 "${helloworld-.world-world}", // first segment must not end with hyphen (-) 95 "${helloworld.-world-world}", // second segment must not start with hyphen (-) 96 "${-hello-world.-world-world-}", // must not start or end with hyphen (-) 97 "${_-_._-_.id}", // cannot use _- in sequence 98 "${0helloworld.world-world}", // interpolated first section shouldn't start with number 99 "${helloworld.9world-world}", // interpolated second section shouldn't start with number 100 "${a-a.a-_a-a.id}", // fails because of -_ in the second segment 101 "${a-a.a--a-a.id}", // fails because of -- in the second segment 102 } 103 for _, invalidMatch := range invalidMatches { 104 match := re.FindStringSubmatch(invalidMatch) 105 assert.True(t, len(match) == 0, "Should be invalid interpolation: %s", invalidMatch) 106 } 107 } 108 109 func TestInterpolationWithPointers(t *testing.T) { 110 fd := "${a}" 111 f := foo{ 112 A: "a", 113 D: &fd, 114 } 115 116 err := expand(&f) 117 require.NoError(t, err) 118 119 assert.Equal(t, "a", f.A) 120 assert.Equal(t, "a", *f.D) 121 } 122 123 func TestInterpolationWithStruct(t *testing.T) { 124 fy := "${e.x}" 125 f := foo{ 126 A: "${e.x}", 127 E: nest{ 128 X: "x", 129 Y: &fy, 130 }, 131 } 132 133 err := expand(&f) 134 require.NoError(t, err) 135 136 assert.Equal(t, "x", f.A) 137 assert.Equal(t, "x", f.E.X) 138 assert.Equal(t, "x", *f.E.Y) 139 } 140 141 func TestInterpolationWithMap(t *testing.T) { 142 f := foo{ 143 A: "${f.a}", 144 F: map[string]string{ 145 "a": "a", 146 "b": "${f.a}", 147 }, 148 } 149 150 err := expand(&f) 151 require.NoError(t, err) 152 153 assert.Equal(t, "a", f.A) 154 assert.Equal(t, "a", f.F["a"]) 155 assert.Equal(t, "a", f.F["b"]) 156 } 157 158 func TestInterpolationWithResursiveVariableReferences(t *testing.T) { 159 f := foo{ 160 A: "a", 161 B: "(${a})", 162 C: "${a} ${b}", 163 } 164 165 err := expand(&f) 166 require.NoError(t, err) 167 168 assert.Equal(t, "a", f.A) 169 assert.Equal(t, "(a)", f.B) 170 assert.Equal(t, "a (a)", f.C) 171 } 172 173 func TestInterpolationVariableLoopError(t *testing.T) { 174 d := "${b}" 175 f := foo{ 176 A: "a", 177 B: "${c}", 178 C: "${d}", 179 D: &d, 180 } 181 182 err := expand(&f) 183 assert.ErrorContains(t, err, "cycle detected in field resolution: b -> c -> d -> b") 184 } 185 186 func TestInterpolationForVariables(t *testing.T) { 187 foo := "abc" 188 bar := "${var.foo} def" 189 apple := "${var.foo} ${var.bar}" 190 config := config.Root{ 191 Variables: map[string]*variable.Variable{ 192 "foo": { 193 Value: &foo, 194 }, 195 "bar": { 196 Value: &bar, 197 }, 198 "apple": { 199 Value: &apple, 200 }, 201 }, 202 Bundle: config.Bundle{ 203 Name: "${var.apple} ${var.foo}", 204 }, 205 } 206 207 err := expand(&config) 208 assert.NoError(t, err) 209 assert.Equal(t, "abc", *(config.Variables["foo"].Value)) 210 assert.Equal(t, "abc def", *(config.Variables["bar"].Value)) 211 assert.Equal(t, "abc abc def", *(config.Variables["apple"].Value)) 212 assert.Equal(t, "abc abc def abc", config.Bundle.Name) 213 } 214 215 func TestInterpolationLoopForVariables(t *testing.T) { 216 foo := "${var.bar}" 217 bar := "${var.foo}" 218 config := config.Root{ 219 Variables: map[string]*variable.Variable{ 220 "foo": { 221 Value: &foo, 222 }, 223 "bar": { 224 Value: &bar, 225 }, 226 }, 227 Bundle: config.Bundle{ 228 Name: "${var.foo}", 229 }, 230 } 231 232 err := expand(&config) 233 assert.ErrorContains(t, err, "cycle detected in field resolution: bundle.name -> var.foo -> var.bar -> var.foo") 234 } 235 236 func TestInterpolationInvalidVariableReference(t *testing.T) { 237 foo := "abc" 238 config := config.Root{ 239 Variables: map[string]*variable.Variable{ 240 "foo": { 241 Value: &foo, 242 }, 243 }, 244 Bundle: config.Bundle{ 245 Name: "${vars.foo}", 246 }, 247 } 248 249 err := expand(&config) 250 assert.ErrorContains(t, err, "could not resolve reference vars.foo") 251 }