github.com/nektos/act@v0.2.83/pkg/exprparser/functions_test.go (about) 1 package exprparser 2 3 import ( 4 "path/filepath" 5 "testing" 6 7 "github.com/nektos/act/pkg/model" 8 "github.com/stretchr/testify/assert" 9 ) 10 11 func TestFunctionContains(t *testing.T) { 12 table := []struct { 13 input string 14 expected interface{} 15 name string 16 }{ 17 {"contains('search', 'item') }}", false, "contains-str-str"}, 18 {`cOnTaInS('Hello', 'll') }}`, true, "contains-str-casing"}, 19 {`contains('HELLO', 'll') }}`, true, "contains-str-casing"}, 20 {`contains('3.141592', 3.14) }}`, true, "contains-str-number"}, 21 {`contains(3.141592, '3.14') }}`, true, "contains-number-str"}, 22 {`contains(3.141592, 3.14) }}`, true, "contains-number-number"}, 23 {`contains(true, 'u') }}`, true, "contains-bool-str"}, 24 {`contains(null, '') }}`, true, "contains-null-str"}, 25 {`contains(fromJSON('["first","second"]'), 'first') }}`, true, "contains-item"}, 26 {`contains(fromJSON('[null,"second"]'), '') }}`, true, "contains-item-null-empty-str"}, 27 {`contains(fromJSON('["","second"]'), null) }}`, true, "contains-item-empty-str-null"}, 28 {`contains(fromJSON('[true,"second"]'), 'true') }}`, false, "contains-item-bool-arr"}, 29 {`contains(fromJSON('["true","second"]'), true) }}`, false, "contains-item-str-bool"}, 30 {`contains(fromJSON('[3.14,"second"]'), '3.14') }}`, true, "contains-item-number-str"}, 31 {`contains(fromJSON('[3.14,"second"]'), 3.14) }}`, true, "contains-item-number-number"}, 32 {`contains(fromJSON('["","second"]'), fromJSON('[]')) }}`, false, "contains-item-str-arr"}, 33 {`contains(fromJSON('["","second"]'), fromJSON('{}')) }}`, false, "contains-item-str-obj"}, 34 {`contains(fromJSON('[{ "first": { "result": "success" }},{ "second": { "result": "success" }}]').first.result, 'success') }}`, true, "multiple-contains-item"}, 35 {`contains(fromJSON('[{ "result": "success" },{ "result": "failure" }]').*.result, 'failure') }}`, true, "multiple-contains-dereferenced-failure-item"}, 36 {`contains(fromJSON('[{ "result": "failure" },{ "result": "success" }]').*.result, 'success') }}`, true, "multiple-contains-dereferenced-success-item"}, 37 {`contains(fromJSON('[{ "result": "failure" },{ "result": "success" }]').*.result, 'notthere') }}`, false, "multiple-contains-dereferenced-missing-item"}, 38 {`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'val1') }}`, true, "multiple-contains-dereferenced-output-item"}, 39 {`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'val2') }}`, true, "multiple-contains-dereferenced-output-item-2"}, 40 {`contains(fromJSON('[{ "result": "failure", "outputs": { "key": "val1" } },{ "result": "success", "outputs": { "key": "val2" } }]').*.outputs.key, 'missing') }}`, false, "multiple-contains-dereferenced-output-misssing-item"}, 41 } 42 43 env := &EvaluationEnvironment{} 44 45 for _, tt := range table { 46 t.Run(tt.name, func(t *testing.T) { 47 output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone) 48 assert.Nil(t, err) 49 50 assert.Equal(t, tt.expected, output) 51 }) 52 } 53 } 54 55 func TestFunctionStartsWith(t *testing.T) { 56 table := []struct { 57 input string 58 expected interface{} 59 name string 60 }{ 61 {"startsWith('search', 'se') }}", true, "startswith-string"}, 62 {"startsWith('search', 'sa') }}", false, "startswith-string"}, 63 {"startsWith('123search', '123s') }}", true, "startswith-string"}, 64 {"startsWith(123, 's') }}", false, "startswith-string"}, 65 {"startsWith(123, '12') }}", true, "startswith-string"}, 66 {"startsWith('123', 12) }}", true, "startswith-string"}, 67 {"startsWith(null, '42') }}", false, "startswith-string"}, 68 {"startsWith('null', null) }}", true, "startswith-string"}, 69 {"startsWith('null', '') }}", true, "startswith-string"}, 70 } 71 72 env := &EvaluationEnvironment{} 73 74 for _, tt := range table { 75 t.Run(tt.name, func(t *testing.T) { 76 output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone) 77 assert.Nil(t, err) 78 79 assert.Equal(t, tt.expected, output) 80 }) 81 } 82 } 83 84 func TestFunctionEndsWith(t *testing.T) { 85 table := []struct { 86 input string 87 expected interface{} 88 name string 89 }{ 90 {"endsWith('search', 'ch') }}", true, "endsWith-string"}, 91 {"endsWith('search', 'sa') }}", false, "endsWith-string"}, 92 {"endsWith('search123s', '123s') }}", true, "endsWith-string"}, 93 {"endsWith(123, 's') }}", false, "endsWith-string"}, 94 {"endsWith(123, '23') }}", true, "endsWith-string"}, 95 {"endsWith('123', 23) }}", true, "endsWith-string"}, 96 {"endsWith(null, '42') }}", false, "endsWith-string"}, 97 {"endsWith('null', null) }}", true, "endsWith-string"}, 98 {"endsWith('null', '') }}", true, "endsWith-string"}, 99 } 100 101 env := &EvaluationEnvironment{} 102 103 for _, tt := range table { 104 t.Run(tt.name, func(t *testing.T) { 105 output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone) 106 assert.Nil(t, err) 107 108 assert.Equal(t, tt.expected, output) 109 }) 110 } 111 } 112 113 func TestFunctionJoin(t *testing.T) { 114 table := []struct { 115 input string 116 expected interface{} 117 name string 118 }{ 119 {"join(fromJSON('[\"a\", \"b\"]'), ',')", "a,b", "join-arr"}, 120 {"join('string', ',')", "string", "join-str"}, 121 {"join(1, ',')", "1", "join-number"}, 122 {"join(null, ',')", "", "join-number"}, 123 {"join(fromJSON('[\"a\", \"b\", null]'), null)", "ab", "join-number"}, 124 {"join(fromJSON('[\"a\", \"b\"]'))", "a,b", "join-number"}, 125 {"join(fromJSON('[\"a\", \"b\", null]'), 1)", "a1b1", "join-number"}, 126 } 127 128 env := &EvaluationEnvironment{} 129 130 for _, tt := range table { 131 t.Run(tt.name, func(t *testing.T) { 132 output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone) 133 assert.Nil(t, err) 134 135 assert.Equal(t, tt.expected, output) 136 }) 137 } 138 } 139 140 func TestFunctionToJSON(t *testing.T) { 141 table := []struct { 142 input string 143 expected interface{} 144 name string 145 }{ 146 {"toJSON(env) }}", "{\n \"key\": \"value\"\n}", "toJSON"}, 147 {"toJSON(null)", "null", "toJSON-null"}, 148 } 149 150 env := &EvaluationEnvironment{ 151 Env: map[string]string{ 152 "key": "value", 153 }, 154 } 155 156 for _, tt := range table { 157 t.Run(tt.name, func(t *testing.T) { 158 output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone) 159 assert.Nil(t, err) 160 161 assert.Equal(t, tt.expected, output) 162 }) 163 } 164 } 165 166 func TestFunctionFromJSON(t *testing.T) { 167 table := []struct { 168 input string 169 expected interface{} 170 name string 171 }{ 172 {"fromJSON('{\"foo\":\"bar\"}') }}", map[string]interface{}{ 173 "foo": "bar", 174 }, "fromJSON"}, 175 } 176 177 env := &EvaluationEnvironment{} 178 179 for _, tt := range table { 180 t.Run(tt.name, func(t *testing.T) { 181 output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone) 182 assert.Nil(t, err) 183 184 assert.Equal(t, tt.expected, output) 185 }) 186 } 187 } 188 189 func TestFunctionHashFiles(t *testing.T) { 190 table := []struct { 191 input string 192 expected interface{} 193 name string 194 }{ 195 {"hashFiles('**/non-extant-files') }}", "", "hash-non-existing-file"}, 196 {"hashFiles('**/non-extant-files', '**/more-non-extant-files') }}", "", "hash-multiple-non-existing-files"}, 197 {"hashFiles('./for-hashing-1.txt') }}", "66a045b452102c59d840ec097d59d9467e13a3f34f6494e539ffd32c1bb35f18", "hash-single-file"}, 198 {"hashFiles('./for-hashing-*.txt') }}", "8e5935e7e13368cd9688fe8f48a0955293676a021562582c7e848dafe13fb046", "hash-multiple-files"}, 199 {"hashFiles('./for-hashing-*.txt', '!./for-hashing-2.txt') }}", "66a045b452102c59d840ec097d59d9467e13a3f34f6494e539ffd32c1bb35f18", "hash-negative-pattern"}, 200 {"hashFiles('./for-hashing-**') }}", "c418ba693753c84115ced0da77f876cddc662b9054f4b129b90f822597ee2f94", "hash-multiple-files-and-directories"}, 201 {"hashFiles('./for-hashing-3/**') }}", "6f5696b546a7a9d6d42a449dc9a56bef244aaa826601ef27466168846139d2c2", "hash-nested-directories"}, 202 {"hashFiles('./for-hashing-3/**/nested-data.txt') }}", "8ecadfb49f7f978d0a9f3a957e9c8da6cc9ab871f5203b5d9f9d1dc87d8af18c", "hash-nested-directories-2"}, 203 } 204 205 env := &EvaluationEnvironment{} 206 207 for _, tt := range table { 208 t.Run(tt.name, func(t *testing.T) { 209 workdir, err := filepath.Abs("testdata") 210 assert.Nil(t, err) 211 output, err := NewInterpeter(env, Config{WorkingDir: workdir}).Evaluate(tt.input, DefaultStatusCheckNone) 212 assert.Nil(t, err) 213 214 assert.Equal(t, tt.expected, output) 215 }) 216 } 217 } 218 219 func TestFunctionFormat(t *testing.T) { 220 table := []struct { 221 input string 222 expected interface{} 223 error interface{} 224 name string 225 }{ 226 {"format('text')", "text", nil, "format-plain-string"}, 227 {"format('Hello {0} {1} {2}!', 'Mona', 'the', 'Octocat')", "Hello Mona the Octocat!", nil, "format-with-placeholders"}, 228 {"format('{{Hello {0} {1} {2}!}}', 'Mona', 'the', 'Octocat')", "{Hello Mona the Octocat!}", nil, "format-with-escaped-braces"}, 229 {"format('{{0}}', 'test')", "{0}", nil, "format-with-escaped-braces"}, 230 {"format('{{{0}}}', 'test')", "{test}", nil, "format-with-escaped-braces-and-value"}, 231 {"format('}}')", "}", nil, "format-output-closing-brace"}, 232 {`format('Hello "{0}" {1} {2} {3} {4}', null, true, -3.14, NaN, Infinity)`, `Hello "" true -3.14 NaN Infinity`, nil, "format-with-primitives"}, 233 {`format('Hello "{0}" {1} {2}', fromJSON('[0, true, "abc"]'), fromJSON('[{"a":1}]'), fromJSON('{"a":{"b":1}}'))`, `Hello "Array" Array Object`, nil, "format-with-complex-types"}, 234 {"format(true)", "true", nil, "format-with-primitive-args"}, 235 {"format('echo Hello {0} ${{Test}}', github.undefined_property)", "echo Hello ${Test}", nil, "format-with-undefined-value"}, 236 {"format('{0}}', '{1}', 'World')", nil, "Closing bracket without opening one. The following format string is invalid: '{0}}'", "format-invalid-format-string"}, 237 {"format('{0', '{1}', 'World')", nil, "Unclosed brackets. The following format string is invalid: '{0'", "format-invalid-format-string"}, 238 {"format('{2}', '{1}', 'World')", "", "The following format string references more arguments than were supplied: '{2}'", "format-invalid-replacement-reference"}, 239 {"format('{2147483648}')", "", "The following format string is invalid: '{2147483648}'", "format-invalid-replacement-reference"}, 240 {"format('{0} {1} {2} {3}', 1.0, 1.1, 1234567890.0, 12345678901234567890.0)", "1 1.1 1234567890 1.23456789012346E+19", nil, "format-floats"}, 241 } 242 243 env := &EvaluationEnvironment{ 244 Github: &model.GithubContext{}, 245 } 246 247 for _, tt := range table { 248 t.Run(tt.name, func(t *testing.T) { 249 output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone) 250 if tt.error != nil { 251 assert.Equal(t, tt.error, err.Error()) 252 } else { 253 assert.Nil(t, err) 254 assert.Equal(t, tt.expected, output) 255 } 256 }) 257 } 258 } 259 260 func TestMapContains(t *testing.T) { 261 env := &EvaluationEnvironment{ 262 Needs: map[string]Needs{ 263 "first-job": { 264 Outputs: map[string]string{}, 265 Result: "success", 266 }, 267 "second-job": { 268 Outputs: map[string]string{}, 269 Result: "failure", 270 }, 271 }, 272 } 273 274 output, err := NewInterpeter(env, Config{}).Evaluate("contains(needs.*.result, 'failure')", DefaultStatusCheckNone) 275 assert.Nil(t, err) 276 277 assert.Equal(t, true, output) 278 }