|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# Test suite for viash_parse_json Bash function (compatibility parser) |
| 4 | +echo "Running viash_parse_json Bash compatibility unit tests..." |
| 5 | + |
| 6 | +# Load the parser |
| 7 | +source src/main/resources/io/viash/languages/bash/ViashParseJsonCompatibility.sh |
| 8 | + |
| 9 | +# Colors for test output |
| 10 | +RED='\033[0;31m' |
| 11 | +GREEN='\033[0;32m' |
| 12 | +RESET='\033[0m' |
| 13 | + |
| 14 | +test_passed=0 |
| 15 | +test_failed=0 |
| 16 | + |
| 17 | +# Test helper function |
| 18 | +test_equal() { |
| 19 | + local description="$1" |
| 20 | + local actual="$2" |
| 21 | + local expected="$3" |
| 22 | + |
| 23 | + if [ "$actual" = "$expected" ]; then |
| 24 | + echo -e "${GREEN}PASS${RESET}: $description" |
| 25 | + ((test_passed++)) |
| 26 | + return 0 |
| 27 | + else |
| 28 | + echo -e "${RED}FAIL${RESET}: $description" |
| 29 | + echo " Expected: $expected" |
| 30 | + echo " Got: $actual" |
| 31 | + ((test_failed++)) |
| 32 | + return 1 |
| 33 | + fi |
| 34 | +} |
| 35 | + |
| 36 | +# Create test JSON file |
| 37 | +test_json=$(mktemp) |
| 38 | +cat >"$test_json" <<'EOF' |
| 39 | +{ |
| 40 | + "par": { |
| 41 | + "input": "file.txt", |
| 42 | + "number": 42, |
| 43 | + "flag": true, |
| 44 | + "empty_value": "", |
| 45 | + "array_simple": ["a", "b", "c"], |
| 46 | + "array_numbers": [1, 2, 3], |
| 47 | + "array_mixed": ["text", 123, true, null], |
| 48 | + "nested": { |
| 49 | + "level1": { |
| 50 | + "nasty_val": "{nasty}", |
| 51 | + "level2": "deep_value" |
| 52 | + } |
| 53 | + }, |
| 54 | + "path_with_spaces": "/path/with spaces/file.txt", |
| 55 | + "quotes": "She said \"hello\"", |
| 56 | + "newlines": "line1\nline2", |
| 57 | + "tabs": "col1\tcol2", |
| 58 | + "string": "text", |
| 59 | + "integer": 123, |
| 60 | + "float": 3.14, |
| 61 | + "bool_true": true, |
| 62 | + "bool_false": false, |
| 63 | + "null_value": null, |
| 64 | + "empty_string": "", |
| 65 | + "zero": 0, |
| 66 | + "empty_array": [], |
| 67 | + "empty_object": {} |
| 68 | + }, |
| 69 | + "meta": { |
| 70 | + "name": "test_component", |
| 71 | + "version": "1.0" |
| 72 | + }, |
| 73 | + "simple_key": "value", |
| 74 | + "number_key": 99, |
| 75 | + "bool_key": false |
| 76 | +} |
| 77 | +EOF |
| 78 | + |
| 79 | +# Parse the JSON |
| 80 | +ViashParseJsonBash <"$test_json" |
| 81 | + |
| 82 | +echo "=== Test 1: Basic key-value pairs ===" |
| 83 | +test_equal "par_input" "$par_input" "file.txt" |
| 84 | +test_equal "par_number" "$par_number" "42" |
| 85 | +test_equal "par_flag" "$par_flag" "true" |
| 86 | +test_equal "par_empty_value" "$par_empty_value" "" |
| 87 | +test_equal "meta_name" "$meta_name" "test_component" |
| 88 | +test_equal "meta_version" "$meta_version" "1.0" |
| 89 | + |
| 90 | +echo |
| 91 | +echo "=== Test 2: Arrays ===" |
| 92 | +test_equal "par_array_simple[0]" "${par_array_simple[0]}" "a" |
| 93 | +test_equal "par_array_simple[1]" "${par_array_simple[1]}" "b" |
| 94 | +test_equal "par_array_simple[2]" "${par_array_simple[2]}" "c" |
| 95 | +test_equal "par_array_numbers[0]" "${par_array_numbers[0]}" "1" |
| 96 | +test_equal "par_array_numbers[1]" "${par_array_numbers[1]}" "2" |
| 97 | +test_equal "par_array_numbers[2]" "${par_array_numbers[2]}" "3" |
| 98 | + |
| 99 | +echo |
| 100 | +echo "=== Test 3: Nested objects (stored as JSON strings) ===" |
| 101 | +# Nested objects should be stored as JSON strings |
| 102 | +test_equal "par_nested exists" "${par_nested:+set}" "set" |
| 103 | +# Simple check that it looks like JSON |
| 104 | +if [[ "$par_nested" =~ "level1" ]] && [[ "$par_nested" =~ "level2" ]]; then |
| 105 | + echo -e "${GREEN}PASS${RESET}: par_nested contains expected nested structure" |
| 106 | + ((test_passed++)) |
| 107 | +else |
| 108 | + echo -e "${RED}FAIL${RESET}: par_nested contains expected nested structure" |
| 109 | + echo " Got: $par_nested" |
| 110 | + ((test_failed++)) |
| 111 | +fi |
| 112 | + |
| 113 | +echo |
| 114 | +echo "=== Test 4: Quoted strings ===" |
| 115 | +test_equal "par_path_with_spaces" "$par_path_with_spaces" "/path/with spaces/file.txt" |
| 116 | +test_equal "par_quotes" "$par_quotes" "She said \"hello\"" |
| 117 | +# Note: In bash, \n and \t are literal strings, not escape sequences |
| 118 | +test_equal "par_newlines" "$par_newlines" "line1\nline2" |
| 119 | +test_equal "par_tabs" "$par_tabs" "col1\tcol2" |
| 120 | + |
| 121 | +echo |
| 122 | +echo "=== Test 5: Type conversions ===" |
| 123 | +test_equal "par_string" "$par_string" "text" |
| 124 | +test_equal "par_integer" "$par_integer" "123" |
| 125 | +test_equal "par_float" "$par_float" "3.14" |
| 126 | +test_equal "par_bool_true" "$par_bool_true" "true" |
| 127 | +test_equal "par_bool_false" "$par_bool_false" "false" |
| 128 | +# null values should leave the variable unset |
| 129 | +test_equal "par_null_value (unset)" "${par_null_value-UNSET}" "UNSET" |
| 130 | + |
| 131 | +echo |
| 132 | +echo "=== Test 6: Root-level values ===" |
| 133 | +test_equal "simple_key" "$simple_key" "value" |
| 134 | +test_equal "number_key" "$number_key" "99" |
| 135 | +test_equal "bool_key" "$bool_key" "false" |
| 136 | + |
| 137 | +echo |
| 138 | +echo "=== Test 7: Edge cases ===" |
| 139 | +test_equal "par_empty_string" "$par_empty_string" "" |
| 140 | +test_equal "par_zero" "$par_zero" "0" |
| 141 | +test_equal "par_empty_array length" "${#par_empty_array[@]}" "0" |
| 142 | + |
| 143 | +# Clean up |
| 144 | +rm -f "$test_json" |
| 145 | + |
| 146 | +## =================================================================== |
| 147 | +## TEST 8: Compact / minified JSON (no whitespace) |
| 148 | +## =================================================================== |
| 149 | +echo |
| 150 | +echo "=== Test 8: Compact JSON (minified) ===" |
| 151 | + |
| 152 | +ViashParseJsonBash <<< '{"par":{"x":"hello","y":42,"z":true,"arr":["a","b"],"nested":{"k":"v"}},"meta":{"name":"comp"}}' |
| 153 | + |
| 154 | +test_equal "compact: par_x" "$par_x" "hello" |
| 155 | +test_equal "compact: par_y" "$par_y" "42" |
| 156 | +test_equal "compact: par_z" "$par_z" "true" |
| 157 | +test_equal "compact: par_arr[0]" "${par_arr[0]}" "a" |
| 158 | +test_equal "compact: par_arr[1]" "${par_arr[1]}" "b" |
| 159 | +test_equal "compact: par_nested contains k" "$(echo "$par_nested" | grep -c '"k"')" "1" |
| 160 | +test_equal "compact: meta_name" "$meta_name" "comp" |
| 161 | + |
| 162 | +## =================================================================== |
| 163 | +## TEST 9: Special characters in strings |
| 164 | +## =================================================================== |
| 165 | +echo |
| 166 | +echo "=== Test 9: Special characters ===" |
| 167 | + |
| 168 | +test_special=$(mktemp) |
| 169 | +cat >"$test_special" <<'EOF' |
| 170 | +{ |
| 171 | + "par": { |
| 172 | + "backtick": "run `id`", |
| 173 | + "dollar": "path is $PATH", |
| 174 | + "subst": "time $(date)", |
| 175 | + "backslash": "C:\\Users\\test", |
| 176 | + "quotes_in_str": "She said \"hi\"", |
| 177 | + "slash": "a/b/c", |
| 178 | + "mixed": "a\\b\"c$d`e" |
| 179 | + } |
| 180 | +} |
| 181 | +EOF |
| 182 | +ViashParseJsonBash <"$test_special" |
| 183 | +rm -f "$test_special" |
| 184 | + |
| 185 | +test_equal "special: backtick" "$par_backtick" 'run `id`' |
| 186 | +test_equal "special: dollar" "$par_dollar" 'path is $PATH' |
| 187 | +test_equal "special: subst" "$par_subst" 'time $(date)' |
| 188 | +test_equal "special: backslash" "$par_backslash" 'C:\Users\test' |
| 189 | +test_equal "special: quotes" "$par_quotes_in_str" 'She said "hi"' |
| 190 | +test_equal "special: slash" "$par_slash" "a/b/c" |
| 191 | +test_equal "special: mixed" "$par_mixed" 'a\b"c$d`e' |
| 192 | + |
| 193 | +## =================================================================== |
| 194 | +## TEST 10: Negative numbers, scientific notation |
| 195 | +## =================================================================== |
| 196 | +echo |
| 197 | +echo "=== Test 10: Numeric edge cases ===" |
| 198 | + |
| 199 | +ViashParseJsonBash <<< '{"par":{"neg":-7,"sci":1.5e10,"neg_sci":-2.5E-3,"big":999999999}}' |
| 200 | + |
| 201 | +test_equal "numeric: neg" "$par_neg" "-7" |
| 202 | +test_equal "numeric: sci" "$par_sci" "1.5e10" |
| 203 | +test_equal "numeric: neg_sci" "$par_neg_sci" "-2.5E-3" |
| 204 | +test_equal "numeric: big" "$par_big" "999999999" |
| 205 | + |
| 206 | +## =================================================================== |
| 207 | +## TEST 11: Mixed-type arrays |
| 208 | +## =================================================================== |
| 209 | +echo |
| 210 | +echo "=== Test 11: Mixed-type arrays ===" |
| 211 | + |
| 212 | +ViashParseJsonBash <<< '{"par":{"mix":["text",123,true,false,null,-5]}}' |
| 213 | + |
| 214 | +test_equal "mixed array[0]" "${par_mix[0]}" "text" |
| 215 | +test_equal "mixed array[1]" "${par_mix[1]}" "123" |
| 216 | +test_equal "mixed array[2]" "${par_mix[2]}" "true" |
| 217 | +test_equal "mixed array[3]" "${par_mix[3]}" "false" |
| 218 | +test_equal "mixed array[4]" "${par_mix[4]}" "null" |
| 219 | +test_equal "mixed array[5]" "${par_mix[5]}" "-5" |
| 220 | +test_equal "mixed array length" "${#par_mix[@]}" "6" |
| 221 | + |
| 222 | +## =================================================================== |
| 223 | +## TEST 12: Empty and minimal JSON |
| 224 | +## =================================================================== |
| 225 | +echo |
| 226 | +echo "=== Test 12: Empty/minimal JSON ===" |
| 227 | + |
| 228 | +ViashParseJsonBash <<< '{}' |
| 229 | +# Should not crash - that's the test |
| 230 | + |
| 231 | +ViashParseJsonBash <<< '{"par":{}}' |
| 232 | +# Should also not crash |
| 233 | + |
| 234 | +ViashParseJsonBash <<< '{"par":{"only":true}}' |
| 235 | +test_equal "minimal: par_only" "$par_only" "true" |
| 236 | + |
| 237 | +## =================================================================== |
| 238 | +## TEST 13: Deeply nested objects stored as JSON |
| 239 | +## =================================================================== |
| 240 | +echo |
| 241 | +echo "=== Test 13: Deeply nested objects ===" |
| 242 | + |
| 243 | +test_deep=$(mktemp) |
| 244 | +cat >"$test_deep" <<'EOF' |
| 245 | +{ |
| 246 | + "par": { |
| 247 | + "config": { |
| 248 | + "db": { |
| 249 | + "host": "localhost", |
| 250 | + "port": 5432 |
| 251 | + }, |
| 252 | + "cache": true |
| 253 | + } |
| 254 | + } |
| 255 | +} |
| 256 | +EOF |
| 257 | +ViashParseJsonBash <"$test_deep" |
| 258 | +rm -f "$test_deep" |
| 259 | + |
| 260 | +test_equal "deep: par_config exists" "${par_config:+set}" "set" |
| 261 | +# Should contain the nested JSON including db and cache |
| 262 | +if [[ "$par_config" =~ "host" ]] && [[ "$par_config" =~ "localhost" ]] && [[ "$par_config" =~ "cache" ]]; then |
| 263 | + echo -e "${GREEN}PASS${RESET}: deep: par_config contains expected structure" |
| 264 | + ((test_passed++)) |
| 265 | +else |
| 266 | + echo -e "${RED}FAIL${RESET}: deep: par_config contains expected structure" |
| 267 | + echo " Got: $par_config" |
| 268 | + ((test_failed++)) |
| 269 | +fi |
| 270 | + |
| 271 | +## =================================================================== |
| 272 | +## TEST 14: Error handling - invalid JSON should fail |
| 273 | +## =================================================================== |
| 274 | +echo |
| 275 | +echo "=== Test 14: Error handling ===" |
| 276 | + |
| 277 | +# Invalid JSON: missing closing brace |
| 278 | +set +e |
| 279 | +stderr=$(ViashParseJsonBash <<< '{"par":{"x": "hello"' 2>&1 >/dev/null) |
| 280 | +exit_code=$? |
| 281 | +set -e |
| 282 | +if [ $exit_code -ne 0 ]; then |
| 283 | + echo -e "${GREEN}PASS${RESET}: invalid JSON exits with error" |
| 284 | + ((test_passed++)) |
| 285 | +else |
| 286 | + echo -e "${RED}FAIL${RESET}: invalid JSON exits with error (got exit code 0)" |
| 287 | + ((test_failed++)) |
| 288 | +fi |
| 289 | + |
| 290 | +# Invalid JSON: trailing garbage |
| 291 | +set +e |
| 292 | +stderr=$(ViashParseJsonBash <<< '{"x": }' 2>&1 >/dev/null) |
| 293 | +exit_code=$? |
| 294 | +set -e |
| 295 | +if [ $exit_code -ne 0 ]; then |
| 296 | + echo -e "${GREEN}PASS${RESET}: malformed value exits with error" |
| 297 | + ((test_passed++)) |
| 298 | +else |
| 299 | + echo -e "${RED}FAIL${RESET}: malformed value exits with error (got exit code 0)" |
| 300 | + ((test_failed++)) |
| 301 | +fi |
| 302 | + |
| 303 | +## =================================================================== |
| 304 | +## TEST 15: Strings with commas, colons, braces (tricky for line parsers) |
| 305 | +## =================================================================== |
| 306 | +echo |
| 307 | +echo "=== Test 15: Tricky string content ===" |
| 308 | + |
| 309 | +test_tricky=$(mktemp) |
| 310 | +cat >"$test_tricky" <<'EOF' |
| 311 | +{ |
| 312 | + "par": { |
| 313 | + "with_comma": "a, b, c", |
| 314 | + "with_colon": "key: value", |
| 315 | + "with_braces": "obj = {x: 1}", |
| 316 | + "with_brackets": "arr = [1, 2]" |
| 317 | + } |
| 318 | +} |
| 319 | +EOF |
| 320 | +ViashParseJsonBash <"$test_tricky" |
| 321 | +rm -f "$test_tricky" |
| 322 | + |
| 323 | +test_equal "tricky: comma" "$par_with_comma" "a, b, c" |
| 324 | +test_equal "tricky: colon" "$par_with_colon" "key: value" |
| 325 | +test_equal "tricky: braces" "$par_with_braces" "obj = {x: 1}" |
| 326 | +test_equal "tricky: brackets" "$par_with_brackets" "arr = [1, 2]" |
| 327 | + |
| 328 | +# Print summary |
| 329 | +echo |
| 330 | +echo "==================================================" |
| 331 | +echo "Tests completed: $((test_passed + test_failed))" |
| 332 | +echo "Tests passed: $test_passed" |
| 333 | +if [ $test_failed -gt 0 ]; then |
| 334 | + echo -e "${RED}Tests failed: $test_failed${RESET}" |
| 335 | + exit 1 |
| 336 | +else |
| 337 | + echo -e "${GREEN}All tests passed!${RESET}" |
| 338 | +fi |
0 commit comments