get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/conf/parse_test.go (about) 1 package conf 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "reflect" 8 "strings" 9 "testing" 10 "time" 11 ) 12 13 // Test to make sure we get what we expect. 14 15 func test(t *testing.T, data string, ex map[string]interface{}) { 16 t.Helper() 17 m, err := Parse(data) 18 if err != nil { 19 t.Fatalf("Received err: %v\n", err) 20 } 21 if m == nil { 22 t.Fatal("Received nil map") 23 } 24 25 if !reflect.DeepEqual(m, ex) { 26 t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, ex) 27 } 28 } 29 30 func TestSimpleTopLevel(t *testing.T) { 31 ex := map[string]interface{}{ 32 "foo": "1", 33 "bar": float64(2.2), 34 "baz": true, 35 "boo": int64(22), 36 } 37 test(t, "foo='1'; bar=2.2; baz=true; boo=22", ex) 38 } 39 40 func TestBools(t *testing.T) { 41 ex := map[string]interface{}{ 42 "foo": true, 43 } 44 test(t, "foo=true", ex) 45 test(t, "foo=TRUE", ex) 46 test(t, "foo=true", ex) 47 test(t, "foo=yes", ex) 48 test(t, "foo=on", ex) 49 } 50 51 var varSample = ` 52 index = 22 53 foo = $index 54 ` 55 56 func TestSimpleVariable(t *testing.T) { 57 ex := map[string]interface{}{ 58 "index": int64(22), 59 "foo": int64(22), 60 } 61 test(t, varSample, ex) 62 } 63 64 var varNestedSample = ` 65 index = 22 66 nest { 67 index = 11 68 foo = $index 69 } 70 bar = $index 71 ` 72 73 func TestNestedVariable(t *testing.T) { 74 ex := map[string]interface{}{ 75 "index": int64(22), 76 "nest": map[string]interface{}{ 77 "index": int64(11), 78 "foo": int64(11), 79 }, 80 "bar": int64(22), 81 } 82 test(t, varNestedSample, ex) 83 } 84 85 func TestMissingVariable(t *testing.T) { 86 _, err := Parse("foo=$index") 87 if err == nil { 88 t.Fatalf("Expected an error for a missing variable, got none") 89 } 90 if !strings.HasPrefix(err.Error(), "variable reference") { 91 t.Fatalf("Wanted a variable reference err, got %q\n", err) 92 } 93 } 94 95 func TestEnvVariable(t *testing.T) { 96 ex := map[string]interface{}{ 97 "foo": int64(22), 98 } 99 evar := "__UNIQ22__" 100 os.Setenv(evar, "22") 101 defer os.Unsetenv(evar) 102 test(t, fmt.Sprintf("foo = $%s", evar), ex) 103 } 104 105 func TestEnvVariableString(t *testing.T) { 106 ex := map[string]interface{}{ 107 "foo": "xyz", 108 } 109 evar := "__UNIQ22__" 110 os.Setenv(evar, "xyz") 111 defer os.Unsetenv(evar) 112 test(t, fmt.Sprintf("foo = $%s", evar), ex) 113 } 114 115 func TestEnvVariableStringStartingWithNumber(t *testing.T) { 116 evar := "__UNIQ22__" 117 os.Setenv(evar, "3xyz") 118 defer os.Unsetenv(evar) 119 120 _, err := Parse("foo = $%s") 121 if err == nil { 122 t.Fatalf("Expected err not being able to process string: %v\n", err) 123 } 124 } 125 126 func TestEnvVariableStringStartingWithNumberAndSizeUnit(t *testing.T) { 127 ex := map[string]interface{}{ 128 "foo": "3Gyz", 129 } 130 evar := "__UNIQ22__" 131 os.Setenv(evar, "3Gyz") 132 defer os.Unsetenv(evar) 133 test(t, fmt.Sprintf("foo = $%s", evar), ex) 134 } 135 136 func TestEnvVariableStringStartingWithNumberUsingQuotes(t *testing.T) { 137 ex := map[string]interface{}{ 138 "foo": "3xyz", 139 } 140 evar := "__UNIQ22__" 141 os.Setenv(evar, "'3xyz'") 142 defer os.Unsetenv(evar) 143 test(t, fmt.Sprintf("foo = $%s", evar), ex) 144 } 145 146 func TestBcryptVariable(t *testing.T) { 147 ex := map[string]interface{}{ 148 "password": "$2a$11$ooo", 149 } 150 test(t, "password: $2a$11$ooo", ex) 151 } 152 153 var easynum = ` 154 k = 8k 155 kb = 4kb 156 ki = 3ki 157 kib = 4ki 158 m = 1m 159 mb = 2MB 160 mi = 2Mi 161 mib = 64MiB 162 g = 2g 163 gb = 22GB 164 gi = 22Gi 165 gib = 22GiB 166 tb = 22TB 167 ti = 22Ti 168 tib = 22TiB 169 pb = 22PB 170 pi = 22Pi 171 pib = 22PiB 172 ` 173 174 func TestConvenientNumbers(t *testing.T) { 175 ex := map[string]interface{}{ 176 "k": int64(8 * 1000), 177 "kb": int64(4 * 1024), 178 "ki": int64(3 * 1024), 179 "kib": int64(4 * 1024), 180 "m": int64(1000 * 1000), 181 "mb": int64(2 * 1024 * 1024), 182 "mi": int64(2 * 1024 * 1024), 183 "mib": int64(64 * 1024 * 1024), 184 "g": int64(2 * 1000 * 1000 * 1000), 185 "gb": int64(22 * 1024 * 1024 * 1024), 186 "gi": int64(22 * 1024 * 1024 * 1024), 187 "gib": int64(22 * 1024 * 1024 * 1024), 188 "tb": int64(22 * 1024 * 1024 * 1024 * 1024), 189 "ti": int64(22 * 1024 * 1024 * 1024 * 1024), 190 "tib": int64(22 * 1024 * 1024 * 1024 * 1024), 191 "pb": int64(22 * 1024 * 1024 * 1024 * 1024 * 1024), 192 "pi": int64(22 * 1024 * 1024 * 1024 * 1024 * 1024), 193 "pib": int64(22 * 1024 * 1024 * 1024 * 1024 * 1024), 194 } 195 test(t, easynum, ex) 196 } 197 198 var sample1 = ` 199 foo { 200 host { 201 ip = '127.0.0.1' 202 port = 4242 203 } 204 servers = [ "a.com", "b.com", "c.com"] 205 } 206 ` 207 208 func TestSample1(t *testing.T) { 209 ex := map[string]interface{}{ 210 "foo": map[string]interface{}{ 211 "host": map[string]interface{}{ 212 "ip": "127.0.0.1", 213 "port": int64(4242), 214 }, 215 "servers": []interface{}{"a.com", "b.com", "c.com"}, 216 }, 217 } 218 test(t, sample1, ex) 219 } 220 221 var cluster = ` 222 cluster { 223 port: 4244 224 225 authorization { 226 user: route_user 227 password: top_secret 228 timeout: 1 229 } 230 231 # Routes are actively solicited and connected to from this server. 232 # Other servers can connect to us if they supply the correct credentials 233 # in their routes definitions from above. 234 235 // Test both styles of comments 236 237 routes = [ 238 nats-route://foo:bar@apcera.me:4245 239 nats-route://foo:bar@apcera.me:4246 240 ] 241 } 242 ` 243 244 func TestSample2(t *testing.T) { 245 ex := map[string]interface{}{ 246 "cluster": map[string]interface{}{ 247 "port": int64(4244), 248 "authorization": map[string]interface{}{ 249 "user": "route_user", 250 "password": "top_secret", 251 "timeout": int64(1), 252 }, 253 "routes": []interface{}{ 254 "nats-route://foo:bar@apcera.me:4245", 255 "nats-route://foo:bar@apcera.me:4246", 256 }, 257 }, 258 } 259 260 test(t, cluster, ex) 261 } 262 263 var sample3 = ` 264 foo { 265 expr = '(true == "false")' 266 text = 'This is a multi-line 267 text block.' 268 } 269 ` 270 271 func TestSample3(t *testing.T) { 272 ex := map[string]interface{}{ 273 "foo": map[string]interface{}{ 274 "expr": "(true == \"false\")", 275 "text": "This is a multi-line\ntext block.", 276 }, 277 } 278 test(t, sample3, ex) 279 } 280 281 var sample4 = ` 282 array [ 283 { abc: 123 } 284 { xyz: "word" } 285 ] 286 ` 287 288 func TestSample4(t *testing.T) { 289 ex := map[string]interface{}{ 290 "array": []interface{}{ 291 map[string]interface{}{"abc": int64(123)}, 292 map[string]interface{}{"xyz": "word"}, 293 }, 294 } 295 test(t, sample4, ex) 296 } 297 298 var sample5 = ` 299 now = 2016-05-04T18:53:41Z 300 gmt = false 301 302 ` 303 304 func TestSample5(t *testing.T) { 305 dt, _ := time.Parse("2006-01-02T15:04:05Z", "2016-05-04T18:53:41Z") 306 ex := map[string]interface{}{ 307 "now": dt, 308 "gmt": false, 309 } 310 test(t, sample5, ex) 311 } 312 313 func TestIncludes(t *testing.T) { 314 ex := map[string]interface{}{ 315 "listen": "127.0.0.1:4222", 316 "authorization": map[string]interface{}{ 317 "ALICE_PASS": "$2a$10$UHR6GhotWhpLsKtVP0/i6.Nh9.fuY73cWjLoJjb2sKT8KISBcUW5q", 318 "BOB_PASS": "$2a$11$dZM98SpGeI7dCFFGSpt.JObQcix8YHml4TBUZoge9R1uxnMIln5ly", 319 "users": []interface{}{ 320 map[string]interface{}{ 321 "user": "alice", 322 "password": "$2a$10$UHR6GhotWhpLsKtVP0/i6.Nh9.fuY73cWjLoJjb2sKT8KISBcUW5q"}, 323 map[string]interface{}{ 324 "user": "bob", 325 "password": "$2a$11$dZM98SpGeI7dCFFGSpt.JObQcix8YHml4TBUZoge9R1uxnMIln5ly"}, 326 }, 327 "timeout": float64(0.5), 328 }, 329 } 330 331 m, err := ParseFile("simple.conf") 332 if err != nil { 333 t.Fatalf("Received err: %v\n", err) 334 } 335 if m == nil { 336 t.Fatal("Received nil map") 337 } 338 339 if !reflect.DeepEqual(m, ex) { 340 t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, ex) 341 } 342 } 343 344 var varIncludedVariablesSample = ` 345 authorization { 346 347 include "./includes/passwords.conf" 348 349 CAROL_PASS: foo 350 351 users = [ 352 {user: alice, password: $ALICE_PASS} 353 {user: bob, password: $BOB_PASS} 354 {user: carol, password: $CAROL_PASS} 355 ] 356 } 357 ` 358 359 func TestIncludeVariablesWithChecks(t *testing.T) { 360 p, err := parse(varIncludedVariablesSample, "", true) 361 if err != nil { 362 t.Fatalf("Received err: %v\n", err) 363 } 364 key := "authorization" 365 m, ok := p.mapping[key] 366 if !ok { 367 t.Errorf("Expected %q to be in the config", key) 368 } 369 expectKeyVal := func(t *testing.T, m interface{}, expectedKey string, expectedVal string, expectedLine, expectedPos int) { 370 t.Helper() 371 tk := m.(*token) 372 v := tk.Value() 373 vv := v.(map[string]interface{}) 374 value, ok := vv[expectedKey] 375 if !ok { 376 t.Errorf("Expected key %q", expectedKey) 377 } 378 tk, ok = value.(*token) 379 if !ok { 380 t.Fatalf("Expected token %v", value) 381 } 382 if tk.Line() != expectedLine { 383 t.Errorf("Expected token to be at line %d, got: %d", expectedLine, tk.Line()) 384 } 385 if tk.Position() != expectedPos { 386 t.Errorf("Expected token to be at position %d, got: %d", expectedPos, tk.Position()) 387 } 388 v = tk.Value() 389 if v != expectedVal { 390 t.Errorf("Expected %q, got: %s", expectedVal, v) 391 } 392 } 393 expectKeyVal(t, m, "ALICE_PASS", "$2a$10$UHR6GhotWhpLsKtVP0/i6.Nh9.fuY73cWjLoJjb2sKT8KISBcUW5q", 2, 1) 394 expectKeyVal(t, m, "BOB_PASS", "$2a$11$dZM98SpGeI7dCFFGSpt.JObQcix8YHml4TBUZoge9R1uxnMIln5ly", 3, 1) 395 expectKeyVal(t, m, "CAROL_PASS", "foo", 6, 3) 396 } 397 398 func TestParserNoInfiniteLoop(t *testing.T) { 399 for _, test := range []string{`A@@Føøøø?˛ø:{øøøø˙˙`, `include "9/�`} { 400 if _, err := Parse(test); err == nil { 401 t.Fatal("expected an error") 402 } else if !strings.Contains(err.Error(), "Unexpected EOF") { 403 t.Fatal("expected unexpected eof error") 404 } 405 } 406 } 407 408 func TestParseWithNoValuesAreInvalid(t *testing.T) { 409 for _, test := range []struct { 410 name string 411 conf string 412 err string 413 }{ 414 { 415 "invalid key without values", 416 `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, 417 "config is invalid (:1:41)", 418 }, 419 { 420 "invalid untrimmed key without values", 421 ` aaaaaaaaaaaaaaaaaaaaaaaaaaa`, 422 "config is invalid (:1:41)", 423 }, 424 { 425 "invalid untrimmed key without values", 426 ` aaaaaaaaaaaaaaaaaaaaaaaaaaa `, 427 "config is invalid (:1:41)", 428 }, 429 { 430 "invalid keys after comments", 431 ` 432 # with comments and no spaces to create key values 433 # is also an invalid config. 434 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 435 `, 436 "config is invalid (:5:25)", 437 }, 438 { 439 "comma separated without values are invalid", 440 ` 441 a,a,a,a,a,a,a,a,a,a,a 442 `, 443 "config is invalid (:3:25)", 444 }, 445 } { 446 t.Run(test.name, func(t *testing.T) { 447 if _, err := parse(test.conf, "", true); err == nil { 448 t.Error("expected an error") 449 } else if !strings.Contains(err.Error(), test.err) { 450 t.Errorf("expected invalid conf error, got: %v", err) 451 } 452 }) 453 } 454 } 455 456 func TestParseWithNoValuesEmptyConfigsAreValid(t *testing.T) { 457 for _, test := range []struct { 458 name string 459 conf string 460 }{ 461 { 462 "empty conf", 463 "", 464 }, 465 { 466 "empty conf with line breaks", 467 ` 468 469 470 `, 471 }, 472 { 473 "just comments with no values", 474 ` 475 # just comments with no values 476 # is still valid. 477 `, 478 }, 479 } { 480 t.Run(test.name, func(t *testing.T) { 481 if _, err := parse(test.conf, "", true); err != nil { 482 t.Errorf("unexpected error: %v", err) 483 } 484 }) 485 } 486 } 487 488 func TestParseWithTrailingBracketsAreValid(t *testing.T) { 489 for _, test := range []struct { 490 name string 491 conf string 492 }{ 493 { 494 "empty conf", 495 "{}", 496 }, 497 { 498 "just comments with no values", 499 ` 500 { 501 # comments in the body 502 } 503 `, 504 }, 505 { 506 // trailing brackets accidentally can become keys, 507 // this is valid since needed to support JSON like configs.. 508 "trailing brackets after config", 509 ` 510 accounts { users = [{}]} 511 } 512 `, 513 }, 514 { 515 "wrapped in brackets", 516 `{ 517 accounts { users = [{}]} 518 } 519 `, 520 }, 521 } { 522 t.Run(test.name, func(t *testing.T) { 523 if _, err := parse(test.conf, "", true); err != nil { 524 t.Errorf("unexpected error: %v", err) 525 } 526 }) 527 } 528 } 529 530 func TestParseWithNoValuesIncludes(t *testing.T) { 531 for _, test := range []struct { 532 input string 533 includes map[string]string 534 err string 535 linepos string 536 }{ 537 { 538 `# includes 539 accounts { 540 foo { include 'foo.conf'} 541 bar { users = [{user = "bar"}] } 542 quux { include 'quux.conf'} 543 } 544 `, 545 map[string]string{ 546 "foo.conf": ``, 547 "quux.conf": `?????????????`, 548 }, 549 "error parsing include file 'quux.conf', config is invalid", 550 "quux.conf:1:1", 551 }, 552 { 553 `# includes 554 accounts { 555 foo { include 'foo.conf'} 556 bar { include 'bar.conf'} 557 quux { include 'quux.conf'} 558 } 559 `, 560 map[string]string{ 561 "foo.conf": ``, // Empty configs are ok 562 "bar.conf": `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`, 563 "quux.conf": ` 564 # just some comments, 565 # and no key values also ok. 566 `, 567 }, 568 "error parsing include file 'bar.conf', config is invalid", 569 "bar.conf:1:34", 570 }, 571 } { 572 t.Run("", func(t *testing.T) { 573 sdir := t.TempDir() 574 f, err := os.CreateTemp(sdir, "nats.conf-") 575 if err != nil { 576 t.Fatal(err) 577 } 578 if err := os.WriteFile(f.Name(), []byte(test.input), 066); err != nil { 579 t.Error(err) 580 } 581 if test.includes != nil { 582 for includeFile, contents := range test.includes { 583 inf, err := os.Create(filepath.Join(sdir, includeFile)) 584 if err != nil { 585 t.Fatal(err) 586 } 587 if err := os.WriteFile(inf.Name(), []byte(contents), 066); err != nil { 588 t.Error(err) 589 } 590 } 591 } 592 if _, err := parse(test.input, f.Name(), true); err == nil { 593 t.Error("expected an error") 594 } else if !strings.Contains(err.Error(), test.err) || !strings.Contains(err.Error(), test.linepos) { 595 t.Errorf("expected invalid conf error, got: %v", err) 596 } 597 }) 598 } 599 } 600 601 func TestJSONParseCompat(t *testing.T) { 602 for _, test := range []struct { 603 name string 604 input string 605 includes map[string]string 606 expected map[string]interface{} 607 }{ 608 { 609 "JSON with nested blocks", 610 ` 611 { 612 "http_port": 8227, 613 "port": 4227, 614 "write_deadline": "1h", 615 "cluster": { 616 "port": 6222, 617 "routes": [ 618 "nats://127.0.0.1:4222", 619 "nats://127.0.0.1:4223", 620 "nats://127.0.0.1:4224" 621 ] 622 } 623 } 624 `, 625 nil, 626 map[string]interface{}{ 627 "http_port": int64(8227), 628 "port": int64(4227), 629 "write_deadline": "1h", 630 "cluster": map[string]interface{}{ 631 "port": int64(6222), 632 "routes": []interface{}{ 633 "nats://127.0.0.1:4222", 634 "nats://127.0.0.1:4223", 635 "nats://127.0.0.1:4224", 636 }, 637 }, 638 }, 639 }, 640 { 641 "JSON with nested blocks", 642 `{ 643 "jetstream": { 644 "store_dir": "/tmp/nats" 645 "max_mem": 1000000, 646 }, 647 "port": 4222, 648 "server_name": "nats1" 649 } 650 `, 651 nil, 652 map[string]interface{}{ 653 "jetstream": map[string]interface{}{ 654 "store_dir": "/tmp/nats", 655 "max_mem": int64(1_000_000), 656 }, 657 "port": int64(4222), 658 "server_name": "nats1", 659 }, 660 }, 661 { 662 "JSON empty object in one line", 663 `{}`, 664 nil, 665 map[string]interface{}{}, 666 }, 667 { 668 "JSON empty object with line breaks", 669 ` 670 { 671 } 672 `, 673 nil, 674 map[string]interface{}{}, 675 }, 676 { 677 "JSON includes", 678 ` 679 accounts { 680 foo { include 'foo.json' } 681 bar { include 'bar.json' } 682 quux { include 'quux.json' } 683 } 684 `, 685 map[string]string{ 686 "foo.json": `{ "users": [ {"user": "foo"} ] }`, 687 "bar.json": `{ 688 "users": [ {"user": "bar"} ] 689 }`, 690 "quux.json": `{}`, 691 }, 692 map[string]interface{}{ 693 "accounts": map[string]interface{}{ 694 "foo": map[string]interface{}{ 695 "users": []interface{}{ 696 map[string]interface{}{ 697 "user": "foo", 698 }, 699 }, 700 }, 701 "bar": map[string]interface{}{ 702 "users": []interface{}{ 703 map[string]interface{}{ 704 "user": "bar", 705 }, 706 }, 707 }, 708 "quux": map[string]interface{}{}, 709 }, 710 }, 711 }, 712 } { 713 t.Run(test.name, func(t *testing.T) { 714 sdir := t.TempDir() 715 f, err := os.CreateTemp(sdir, "nats.conf-") 716 if err != nil { 717 t.Fatal(err) 718 } 719 if err := os.WriteFile(f.Name(), []byte(test.input), 066); err != nil { 720 t.Error(err) 721 } 722 if test.includes != nil { 723 for includeFile, contents := range test.includes { 724 inf, err := os.Create(filepath.Join(sdir, includeFile)) 725 if err != nil { 726 t.Fatal(err) 727 } 728 if err := os.WriteFile(inf.Name(), []byte(contents), 066); err != nil { 729 t.Error(err) 730 } 731 } 732 } 733 m, err := ParseFile(f.Name()) 734 if err != nil { 735 t.Fatalf("Unexpected error: %v", err) 736 } 737 if !reflect.DeepEqual(m, test.expected) { 738 t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, test.expected) 739 } 740 }) 741 } 742 }