general.rs
1 use korni::{parse, parse_with_options, Entry, ParseOptions, QuoteType}; 2 3 // Tests extracted from src/lib.rs 4 5 #[test] 6 fn test_basic_parse() { 7 let input = "KEY=VALUE"; 8 let entries = parse(input); 9 10 assert_eq!(entries.len(), 1); 11 let kv = entries[0].as_pair().unwrap(); 12 13 assert_eq!(kv.key, "KEY"); 14 assert_eq!(kv.value, "VALUE"); 15 assert!(!kv.is_exported); 16 } 17 18 #[test] 19 fn test_strict_whitespace() { 20 let input = "KEY = VALUE"; // Invalid 21 let entries = parse(input); 22 23 assert_eq!(entries.len(), 1); 24 match &entries[0] { 25 Entry::Error(e) => assert!(e 26 .to_string() 27 .contains("Whitespace not allowed between key and equals")), 28 _ => panic!("Should be error"), 29 } 30 } 31 32 #[test] 33 fn test_export_prefix() { 34 let input = "export DB_URL=postgres"; 35 let entries = parse(input); 36 37 assert_eq!(entries.len(), 1); 38 let kv = entries[0].as_pair().unwrap(); 39 assert_eq!(kv.key, "DB_URL"); 40 assert_eq!(kv.value, "postgres"); 41 assert!(kv.is_exported); 42 } 43 44 #[test] 45 fn test_export_no_space() { 46 let input = "exportKey=val"; 47 let entries = parse(input); 48 let kv = entries[0].as_pair().unwrap(); 49 assert_eq!(kv.key, "exportKey"); 50 assert!(!kv.is_exported); 51 } 52 53 #[test] 54 fn test_tab_after_equals_error() { 55 // Spec 4.1.2: Tab after = is also forbidden 56 let input = "KEY=\tvalue"; 57 let entries = parse(input); 58 match &entries[0] { 59 Entry::Error(e) => assert!(e 60 .to_string() 61 .contains("Whitespace not allowed after equals")), 62 _ => panic!("Should be error"), 63 } 64 } 65 66 #[test] 67 fn test_debug_simple() { 68 // Test basic escaping logic 69 // "a" -> a 70 // "\n" -> newline 71 // "a\"b" -> a"b 72 73 let input = r#"KEY="a\"b""#; 74 let entries = parse(input); 75 let kv = entries[0].as_pair().unwrap(); 76 assert_eq!(kv.value, "a\"b"); // Escaped quote becomes literal quote 77 78 // Literal backslash + char 79 // "a\b" -> a\b (since \b is not special) 80 let input2 = r#"KEY="a\b""#; 81 let entries2 = parse(input2); 82 let kv2 = entries2[0].as_pair().unwrap(); 83 assert_eq!(kv2.value, r#"a\b"#); 84 } 85 86 #[test] 87 fn test_escaped_quotes_and_content() { 88 // Unquoted value with quotes inside are literal 89 // Remove space to comply with Spec 4.2.2 (unquoted ends at whitespace) 90 let input = r#"JSON={"key":"val"}"#; 91 let entries = parse(input); 92 let kv = entries[0].as_pair().unwrap(); 93 assert_eq!(kv.value, r#"{"key":"val"}"#); 94 assert_eq!(kv.quote, QuoteType::None); 95 96 // Quoted value with valid escapes 97 // Per Spec 4.2.4: \" → Literal Double Quote (") 98 // So "{\"key\": \"val\"}" becomes {"key": "val"} 99 let input2 = r#"JSON="{\"key\": \"val\"}""#; 100 let entries2 = parse(input2); 101 let kv2 = entries2[0].as_pair().unwrap(); 102 // Escaped quotes become literal quotes 103 assert_eq!(kv2.value, r#"{"key": "val"}"#); 104 assert_eq!(kv2.quote, QuoteType::Double); 105 } 106 107 #[test] 108 fn test_backslash_continuation_strict() { 109 // With strict shell rules: 110 // "Hello \ # comment" -> "Hello" (Space terminates value) 111 let input = "MESSAGE=Hello \\ # comment\nWorld"; 112 let entries = parse(input); 113 let kv = entries[0].as_pair().unwrap(); 114 assert_eq!(kv.value, "Hello"); 115 116 // "Val\" (no space) -> Continuation works 117 let input2 = "KEY=Val\\\nNext"; 118 let entries2 = parse(input2); 119 let kv2 = entries2[0].as_pair().unwrap(); 120 assert_eq!(kv2.value, "ValNext"); 121 } 122 123 #[test] 124 fn test_multiline_creation_strict() { 125 // "KEY=Line1 \" -> "Line1" (Space terminates) 126 let input = "KEY=Line1 \\\n Line2"; 127 let entries = parse(input); 128 let kv = entries[0].as_pair().unwrap(); 129 assert_eq!(kv.value, "Line1"); 130 } 131 132 // ======== Additional Spec Compliance Tests ======== 133 134 #[test] 135 fn test_inline_comment_after_quoted_value() { 136 // Spec 4.3.2: Inline comments allowed after closing quote 137 let input = r#"KEY="value" # this is a comment"#; 138 let entries = parse(input); 139 let kv = entries[0].as_pair().unwrap(); 140 assert_eq!(kv.value, "value"); 141 assert_eq!(kv.quote, QuoteType::Double); 142 } 143 144 #[test] 145 fn test_inline_comment_after_single_quoted_value() { 146 // Spec 4.3.2: Inline comments allowed after closing single quote 147 let input = "KEY='value' # this is a comment"; 148 let entries = parse(input); 149 let kv = entries[0].as_pair().unwrap(); 150 assert_eq!(kv.value, "value"); 151 assert_eq!(kv.quote, QuoteType::Single); 152 } 153 154 #[test] 155 fn test_no_space_before_hash_after_quote() { 156 // Hash directly after quote without space - still ignored 157 let input = r##"KEY="value"#comment"##; 158 let entries = parse(input); 159 let kv = entries[0].as_pair().unwrap(); 160 assert_eq!(kv.value, "value"); 161 } 162 163 #[test] 164 fn test_trailing_whitespace_after_quote() { 165 // Whitespace after closing quote is ignored 166 let input = "KEY=\"value\" "; 167 let entries = parse(input); 168 let kv = entries[0].as_pair().unwrap(); 169 assert_eq!(kv.value, "value"); 170 } 171 172 #[test] 173 fn test_trailing_junk_after_quote() { 174 // Any content after closing quote is ignored 175 let input = "KEY=\"value\" extra junk"; 176 let entries = parse(input); 177 let kv = entries[0].as_pair().unwrap(); 178 assert_eq!(kv.value, "value"); 179 } 180 181 #[test] 182 fn test_single_quoted_literal() { 183 // Spec 4.2.3: Single quotes = literal, no escaping 184 let input = "KEY='hello\\nworld'"; 185 let entries = parse(input); 186 let kv = entries[0].as_pair().unwrap(); 187 // \n is NOT interpreted as newline in single quotes 188 assert_eq!(kv.value, "hello\\nworld"); 189 assert_eq!(kv.quote, QuoteType::Single); 190 } 191 192 #[test] 193 fn test_empty_values() { 194 // Spec 4.2.1: Empty values 195 let input = "EMPTY="; 196 let entries = parse(input); 197 let kv = entries[0].as_pair().unwrap(); 198 assert_eq!(kv.value, ""); 199 200 let input2 = r#"EMPTY2="""#; 201 let entries2 = parse(input2); 202 let kv2 = entries2[0].as_pair().unwrap(); 203 assert_eq!(kv2.value, ""); 204 205 let input3 = "EMPTY3=''"; 206 let entries3 = parse(input3); 207 let kv3 = entries3[0].as_pair().unwrap(); 208 assert_eq!(kv3.value, ""); 209 } 210 211 #[test] 212 fn test_hash_in_unquoted_value() { 213 // Spec 4.3.2: # preceded by space = comment 214 let input = "URL=http://example.com#anchor"; 215 let entries = parse(input); 216 let kv = entries[0].as_pair().unwrap(); 217 assert_eq!(kv.value, "http://example.com#anchor"); // No space before # 218 219 let input2 = "COLOR=#333"; 220 let entries2 = parse(input2); 221 let kv2 = entries2[0].as_pair().unwrap(); 222 assert_eq!(kv2.value, "#333"); 223 224 let input3 = "KEY=value #comment"; 225 let entries3 = parse(input3); 226 let kv3 = entries3[0].as_pair().unwrap(); 227 assert_eq!(kv3.value, "value"); // Comment stripped 228 } 229 230 #[test] 231 fn test_hash_in_quoted_value() { 232 // Spec 4.3.2: # inside quotes is always literal 233 let input = r#"KEY="value #not a comment""#; 234 let entries = parse(input); 235 let kv = entries[0].as_pair().unwrap(); 236 assert_eq!(kv.value, "value #not a comment"); 237 } 238 239 #[test] 240 fn test_multiple_equals() { 241 // Spec 4.1.2: First = separates, rest are value 242 let input = "KEY=value=with=equals"; 243 let entries = parse(input); 244 let kv = entries[0].as_pair().unwrap(); 245 assert_eq!(kv.value, "value=with=equals"); 246 247 // Spec 4.1.2: Double equals immediately after key is invalid 248 let input2 = "KEY==value"; 249 let entries2 = parse(input2); 250 match &entries2[0] { 251 Entry::Error(e) => assert!(e.to_string().contains("Double equals sign detected")), 252 _ => panic!("Should be double equals error"), 253 } 254 } 255 256 #[test] 257 fn test_double_quote_escape_sequences() { 258 // Spec 4.2.4: Escape sequences 259 let input = r#"KEY="line1\nline2\ttab\\backslash""#; 260 let entries = parse(input); 261 let kv = entries[0].as_pair().unwrap(); 262 assert_eq!(kv.value, "line1\nline2\ttab\\backslash"); 263 } 264 265 #[test] 266 fn test_quoted_multiline() { 267 // Spec 5.1: Quoted multiline preserves newlines 268 let input = "KEY=\"line1\nline2\""; 269 let entries = parse(input); 270 let kv = entries[0].as_pair().unwrap(); 271 assert_eq!(kv.value, "line1\nline2"); 272 } 273 274 #[test] 275 fn test_unclosed_quote_error() { 276 let input = r#"KEY="unclosed"#; 277 let entries = parse(input); 278 match &entries[0] { 279 Entry::Error(e) => assert!(e.to_string().contains("Unclosed")), 280 _ => panic!("Should be error"), 281 } 282 } 283 284 #[test] 285 fn test_utf8_bom_at_start() { 286 // Spec 3.1: BOM at start is stripped 287 let input = "\u{FEFF}KEY=value"; 288 let entries = parse(input); 289 let kv = entries[0].as_pair().unwrap(); 290 assert_eq!(kv.key, "KEY"); 291 assert_eq!(kv.value, "value"); 292 } 293 294 #[test] 295 fn test_comment_lines() { 296 let input = "# This is a comment\nKEY=value"; 297 let entries = parse_with_options( 298 input, 299 ParseOptions { 300 include_comments: true, 301 track_positions: false, 302 }, 303 ); 304 assert_eq!(entries.len(), 2); 305 assert!(matches!(entries[0], Entry::Comment(_))); 306 assert!(entries[1].as_pair().is_some()); 307 } 308 309 #[test] 310 fn test_empty_lines() { 311 let input = "\n\nKEY=value\n\n"; 312 let entries = parse(input); 313 let pairs: Vec<_> = entries.iter().filter(|e| e.as_pair().is_some()).collect(); 314 assert_eq!(pairs.len(), 1); 315 } 316 317 // ======== Spec 4.1.1: Export Prefix Edge Cases ======== 318 319 #[test] 320 fn test_export_with_tab() { 321 // Spec 4.1.1: export followed by tab is valid 322 let input = "export\tKEY=value"; 323 let entries = parse(input); 324 let kv = entries[0].as_pair().unwrap(); 325 assert_eq!(kv.key, "KEY"); 326 assert!(kv.is_exported); 327 } 328 329 #[test] 330 fn test_leading_whitespace_before_export() { 331 // Spec 4.1.1: Leading whitespace before export is allowed 332 let input = " export KEY=value"; 333 let entries = parse(input); 334 let kv = entries[0].as_pair().unwrap(); 335 assert_eq!(kv.key, "KEY"); 336 assert!(kv.is_exported); 337 } 338 339 #[test] 340 fn test_export_underscore_key() { 341 // Spec 4.1.1: export_KEY is a regular key (underscore, not space) 342 let input = "export_KEY=value"; 343 let entries = parse(input); 344 let kv = entries[0].as_pair().unwrap(); 345 assert_eq!(kv.key, "export_KEY"); 346 assert!(!kv.is_exported); 347 } 348 349 #[test] 350 fn test_exported_value_key() { 351 // exportedValue is a regular key 352 let input = "exportedValue=value"; 353 let entries = parse(input); 354 let kv = entries[0].as_pair().unwrap(); 355 assert_eq!(kv.key, "exportedValue"); 356 assert!(!kv.is_exported); 357 } 358 359 // ======== Spec 4.1.2: Key Edge Cases ======== 360 361 #[test] 362 fn test_underscore_start_key() { 363 // Spec 4.1.2: Keys can start with underscore 364 let input = "_PRIVATE=secret"; 365 let entries = parse(input); 366 let kv = entries[0].as_pair().unwrap(); 367 assert_eq!(kv.key, "_PRIVATE"); 368 } 369 370 #[test] 371 fn test_key_with_numbers() { 372 let input = "CONFIG123=value"; 373 let entries = parse(input); 374 let kv = entries[0].as_pair().unwrap(); 375 assert_eq!(kv.key, "CONFIG123"); 376 } 377 378 #[test] 379 fn test_key_starting_with_digit_error() { 380 // Spec 4.1.2: Keys MUST NOT start with digit 381 let input = "123KEY=value"; 382 let entries = parse(input); 383 match &entries[0] { 384 Entry::Error(e) => assert!(e.to_string().contains("digit")), 385 _ => panic!("Should be error"), 386 } 387 } 388 389 #[test] 390 fn test_leading_whitespace_before_key() { 391 // Spec 4.1.2: Leading whitespace is stripped 392 let input = " KEY=value"; 393 let entries = parse(input); 394 let kv = entries[0].as_pair().unwrap(); 395 assert_eq!(kv.key, "KEY"); 396 assert_eq!(kv.value, "value"); 397 } 398 399 #[test] 400 fn test_tab_before_key() { 401 let input = "\tKEY=value"; 402 let entries = parse(input); 403 let kv = entries[0].as_pair().unwrap(); 404 assert_eq!(kv.key, "KEY"); 405 } 406 407 #[test] 408 fn test_lowercase_key() { 409 // Spec 4.1.2: Lowercase keys are valid 410 let input = "database_url=postgres://localhost"; 411 let entries = parse(input); 412 let kv = entries[0].as_pair().unwrap(); 413 assert_eq!(kv.key, "database_url"); 414 } 415 416 // ======== Spec 4.2.2: Unquoted Value Edge Cases ======== 417 418 #[test] 419 fn test_unquoted_with_equals() { 420 // DATABASE_URL with query params 421 let input = "DATABASE_URL=postgresql://user:pass@host:5432/db?param=value"; 422 let entries = parse(input); 423 let kv = entries[0].as_pair().unwrap(); 424 assert_eq!(kv.value, "postgresql://user:pass@host:5432/db?param=value"); 425 } 426 427 #[test] 428 fn test_unquoted_backslash_literal() { 429 // Spec 4.2.2: Backslashes in unquoted are literal (except at EOL) 430 let input = "PATH=C:\\Users\\Name"; 431 let entries = parse(input); 432 let kv = entries[0].as_pair().unwrap(); 433 assert_eq!(kv.value, "C:\\Users\\Name"); 434 } 435 436 #[test] 437 fn test_unquoted_escape_n_literal() { 438 // Spec 4.2.2: \n in unquoted is literal backslash + n 439 let input = "KEY=foo\\nbar"; 440 let entries = parse(input); 441 let kv = entries[0].as_pair().unwrap(); 442 assert_eq!(kv.value, "foo\\nbar"); 443 } 444 445 // ======== Spec 4.2.3: Single Quote Edge Cases ======== 446 447 #[test] 448 fn test_single_quote_dollar_literal() { 449 // Spec 4.2.3: $ is literal in single quotes 450 let input = "KEY='${VAR}'"; 451 let entries = parse(input); 452 let kv = entries[0].as_pair().unwrap(); 453 assert_eq!(kv.value, "${VAR}"); 454 } 455 456 #[test] 457 fn test_single_quote_hash_literal() { 458 // Spec 4.2.3: # is literal in single quotes 459 let input = "KEY='#333'"; 460 let entries = parse(input); 461 let kv = entries[0].as_pair().unwrap(); 462 assert_eq!(kv.value, "#333"); 463 } 464 465 #[test] 466 fn test_single_quote_multiline() { 467 // Spec 4.2.3/5.1: Single quotes preserve newlines 468 let input = "KEY='Line 1\nLine 2\nLine 3'"; 469 let entries = parse(input); 470 let kv = entries[0].as_pair().unwrap(); 471 assert_eq!(kv.value, "Line 1\nLine 2\nLine 3"); 472 } 473 474 #[test] 475 fn test_single_quote_unclosed_error() { 476 let input = "KEY='unclosed"; 477 let entries = parse(input); 478 match &entries[0] { 479 Entry::Error(e) => assert!(e.to_string().contains("Unclosed")), 480 _ => panic!("Should be error"), 481 } 482 } 483 484 // ======== Spec 4.2.4: Double Quote Edge Cases ======== 485 486 #[test] 487 fn test_double_quote_escaped_dollar() { 488 // Spec 4.2.4: \$ produces literal $ 489 let input = r#"KEY="\${NOT_VAR}""#; 490 let entries = parse(input); 491 let kv = entries[0].as_pair().unwrap(); 492 assert_eq!(kv.value, "${NOT_VAR}"); 493 } 494 495 #[test] 496 fn test_double_quote_carriage_return() { 497 // Spec 4.2.4: \r produces carriage return 498 let input = r#"KEY="line1\r\nline2""#; 499 let entries = parse(input); 500 let kv = entries[0].as_pair().unwrap(); 501 assert_eq!(kv.value, "line1\r\nline2"); 502 } 503 504 #[test] 505 fn test_double_quote_consecutive_backslashes() { 506 // Spec 4.2.4: \\\\ produces \\ 507 let input = r#"KEY="value\\\\""#; 508 let entries = parse(input); 509 let kv = entries[0].as_pair().unwrap(); 510 assert_eq!(kv.value, "value\\\\"); 511 } 512 513 #[test] 514 fn test_double_quote_unknown_escape() { 515 // Spec 4.2.4: Unknown escapes preserved literally 516 let input = r#"KEY="\a\x\0""#; 517 let entries = parse(input); 518 let kv = entries[0].as_pair().unwrap(); 519 assert_eq!(kv.value, "\\a\\x\\0"); 520 } 521 522 #[test] 523 fn test_nested_quotes() { 524 // Spec 4.2.4: Opposite quote type is literal 525 let input = r#"KEY="outer 'inner' end""#; 526 let entries = parse(input); 527 let kv = entries[0].as_pair().unwrap(); 528 assert_eq!(kv.value, "outer 'inner' end"); 529 530 let input2 = "KEY='outer \"inner\" end'"; 531 let entries2 = parse(input2); 532 let kv2 = entries2[0].as_pair().unwrap(); 533 assert_eq!(kv2.value, "outer \"inner\" end"); 534 } 535 536 #[test] 537 fn test_space_after_equals_error() { 538 // Spec 4.1.2: Space after = is forbidden 539 let input = r#"KEY= "value""#; 540 let entries = parse(input); 541 match &entries[0] { 542 Entry::Error(e) => assert!(e 543 .to_string() 544 .contains("Whitespace not allowed after equals")), 545 _ => panic!("Should be error"), 546 } 547 } 548 549 #[test] 550 fn test_quotes_within_unquoted() { 551 // Spec 4.2.4: Quotes in unquoted values are literal 552 let input = r#"KEY=foo"bar"baz"#; 553 let entries = parse(input); 554 let kv = entries[0].as_pair().unwrap(); 555 assert_eq!(kv.value, r#"foo"bar"baz"#); 556 } 557 558 // ======== Spec 4.3: Comment Edge Cases ======== 559 560 #[test] 561 fn test_tab_before_hash_comment() { 562 // Spec 4.3.2: Tab before # also triggers comment 563 let input = "KEY=value\t#comment"; 564 let entries = parse(input); 565 let kv = entries[0].as_pair().unwrap(); 566 assert_eq!(kv.value, "value"); 567 } 568 569 #[test] 570 fn test_multiple_spaces_before_hash() { 571 let input = "KEY=value #comment"; 572 let entries = parse(input); 573 let kv = entries[0].as_pair().unwrap(); 574 assert_eq!(kv.value, "value"); 575 } 576 577 #[test] 578 fn test_hash_literal_start_of_value() { 579 // Spec 4.2.2: # at start of value (no space before) is literal 580 let input = "KEY=#not a comment"; 581 let entries = parse(input); 582 // Strict Mode: Terminates at first space. 583 // So "KEY=#not" -> "#not" 584 // " a comment" is ignored. 585 let kv = entries[0].as_pair().unwrap(); 586 assert_eq!(kv.value, "#not"); 587 } 588 589 #[test] 590 fn test_api_key_with_hash() { 591 // Spec 4.3.2: # not preceded by space is literal 592 let input = "API_KEY=sk-abc123#prod"; 593 let entries = parse(input); 594 let kv = entries[0].as_pair().unwrap(); 595 assert_eq!(kv.value, "sk-abc123#prod"); 596 } 597 598 // ======== Spec 4.4: Interpolation (Literal) ======== 599 600 #[test] 601 fn test_dollar_var_literal_unquoted() { 602 // Spec 4.4: Interpolation is NOT performed 603 let input = "DATABASE_URL=${DB_HOST}:${DB_PORT}/${DB_NAME}"; 604 let entries = parse(input); 605 let kv = entries[0].as_pair().unwrap(); 606 assert_eq!(kv.value, "${DB_HOST}:${DB_PORT}/${DB_NAME}"); 607 } 608 609 #[test] 610 fn test_dollar_at_end() { 611 let input = "KEY=value$"; 612 let entries = parse(input); 613 let kv = entries[0].as_pair().unwrap(); 614 assert_eq!(kv.value, "value$"); 615 } 616 617 #[test] 618 fn test_dollar_before_numbers() { 619 let input = "KEY=$123"; 620 let entries = parse(input); 621 let kv = entries[0].as_pair().unwrap(); 622 assert_eq!(kv.value, "$123"); 623 } 624 625 // ======== Spec 5.2: Backslash Continuation Edge Cases ======== 626 627 #[test] 628 fn test_multiple_continuations() { 629 let input = "PATH=/usr/local/bin:\\\n/usr/bin:\\\n/bin"; 630 let entries = parse(input); 631 let kv = entries[0].as_pair().unwrap(); 632 assert_eq!(kv.value, "/usr/local/bin:/usr/bin:/bin"); 633 } 634 635 #[test] 636 fn test_continuation_preserves_leading_space() { 637 // Spec 5.2 (Revised): Backslash continuation without quotes requires CONTIGUOUS backslash. 638 // "COMMAND=docker run \" -> "docker" 639 // To get "docker run...", one must use quotes: "COMMAND='docker run...'" 640 let input = "COMMAND=docker run \\\n --detach \\\n nginx"; 641 let entries = parse(input); 642 let kv = entries[0].as_pair().unwrap(); 643 assert_eq!(kv.value, "docker"); 644 } 645 646 // ======== Spec Edge Cases: Mixed Scenarios ======== 647 648 #[test] 649 fn test_empty_file() { 650 let input = ""; 651 let entries = parse(input); 652 assert_eq!(entries.len(), 0); 653 } 654 655 #[test] 656 fn test_only_comments() { 657 let input = "# Comment 1\n# Comment 2"; 658 let entries = parse(input); 659 let pairs: Vec<_> = entries.iter().filter(|e| e.as_pair().is_some()).collect(); 660 assert_eq!(pairs.len(), 0); 661 } 662 663 #[test] 664 fn test_only_whitespace() { 665 let input = " \n\t\n "; 666 let entries = parse(input); 667 let pairs: Vec<_> = entries.iter().filter(|e| e.as_pair().is_some()).collect(); 668 assert_eq!(pairs.len(), 0); 669 } 670 671 #[test] 672 fn test_indented_comment() { 673 let input = " # Indented comment\nKEY=value"; 674 let entries = parse_with_options( 675 input, 676 ParseOptions { 677 include_comments: true, 678 track_positions: false, 679 }, 680 ); 681 assert!(matches!(entries[0], Entry::Comment(_))); 682 let kv = entries[1].as_pair().unwrap(); 683 assert_eq!(kv.key, "KEY"); 684 } 685 686 #[test] 687 fn test_case_sensitive_keys() { 688 // Spec 4.1.2: Keys are case-sensitive 689 let input = "API_KEY=value1\napi_key=value2"; 690 let entries = parse(input); 691 // Filter to only pairs 692 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 693 assert_eq!(pairs.len(), 2, "Expected 2 key-value pairs"); 694 assert_eq!(pairs[0].key, "API_KEY"); 695 assert_eq!(pairs[1].key, "api_key"); 696 // They are distinct keys 697 assert_ne!(pairs[0].key, pairs[1].key); 698 } 699 700 #[test] 701 fn test_pem_key_multiline() { 702 // Real-world scenario: PEM key in double quotes 703 let input = r#"PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- 704 MIIEpQIBAAKCAQEA3Tz2MR7SZiAMfQyuvBjM9Oi.. 705 -----END RSA PRIVATE KEY-----""#; 706 let entries = parse(input); 707 let kv = entries[0].as_pair().unwrap(); 708 assert!(kv.value.starts_with("-----BEGIN RSA PRIVATE KEY-----")); 709 assert!(kv.value.ends_with("-----END RSA PRIVATE KEY-----")); 710 assert!(kv.value.contains('\n')); 711 } 712 713 #[test] 714 fn test_json_in_double_quotes() { 715 let input = r#"CONFIG="{\"enable\": true, \"retries\": 3}""#; 716 let entries = parse(input); 717 let kv = entries[0].as_pair().unwrap(); 718 assert_eq!(kv.value, r#"{"enable": true, "retries": 3}"#); 719 } 720 721 // ======== ADDITIONAL TESTS FOR 150+ COVERAGE ======== 722 723 // --- Escape Sequences Individual Tests --- 724 725 #[test] 726 fn test_escape_n_only() { 727 let input = r#"KEY="\n""#; 728 let entries = parse(input); 729 let kv = entries[0].as_pair().unwrap(); 730 assert_eq!(kv.value, "\n"); 731 } 732 733 #[test] 734 fn test_escape_r_only() { 735 let input = r#"KEY="\r""#; 736 let entries = parse(input); 737 let kv = entries[0].as_pair().unwrap(); 738 assert_eq!(kv.value, "\r"); 739 } 740 741 #[test] 742 fn test_escape_t_only() { 743 let input = r#"KEY="\t""#; 744 let entries = parse(input); 745 let kv = entries[0].as_pair().unwrap(); 746 assert_eq!(kv.value, "\t"); 747 } 748 749 #[test] 750 fn test_escape_backslash_only() { 751 let input = r#"KEY="\\""#; 752 let entries = parse(input); 753 let kv = entries[0].as_pair().unwrap(); 754 assert_eq!(kv.value, "\\"); 755 } 756 757 #[test] 758 fn test_escape_quote_only() { 759 let input = r#"KEY="\"""#; 760 let entries = parse(input); 761 let kv = entries[0].as_pair().unwrap(); 762 assert_eq!(kv.value, "\""); 763 } 764 765 #[test] 766 fn test_escape_dollar_only() { 767 let input = r#"KEY="\$""#; 768 let entries = parse(input); 769 let kv = entries[0].as_pair().unwrap(); 770 assert_eq!(kv.value, "$"); 771 } 772 773 #[test] 774 fn test_escape_unknown_a() { 775 let input = r#"KEY="\a""#; 776 let entries = parse(input); 777 let kv = entries[0].as_pair().unwrap(); 778 assert_eq!(kv.value, "\\a"); 779 } 780 781 #[test] 782 fn test_escape_unknown_b() { 783 let input = r#"KEY="\b""#; 784 let entries = parse(input); 785 let kv = entries[0].as_pair().unwrap(); 786 assert_eq!(kv.value, "\\b"); 787 } 788 789 #[test] 790 fn test_escape_unknown_f() { 791 let input = r#"KEY="\f""#; 792 let entries = parse(input); 793 let kv = entries[0].as_pair().unwrap(); 794 assert_eq!(kv.value, "\\f"); 795 } 796 797 #[test] 798 fn test_escape_unknown_v() { 799 let input = r#"KEY="\v""#; 800 let entries = parse(input); 801 let kv = entries[0].as_pair().unwrap(); 802 assert_eq!(kv.value, "\\v"); 803 } 804 805 #[test] 806 fn test_escape_unknown_zero() { 807 let input = r#"KEY="\0""#; 808 let entries = parse(input); 809 let kv = entries[0].as_pair().unwrap(); 810 assert_eq!(kv.value, "\\0"); 811 } 812 813 #[test] 814 fn test_escape_unknown_x() { 815 let input = r#"KEY="\x41""#; 816 let entries = parse(input); 817 let kv = entries[0].as_pair().unwrap(); 818 assert_eq!(kv.value, "\\x41"); 819 } 820 821 #[test] 822 fn test_escape_mixed_sequence() { 823 let input = r#"KEY="a\nb\tc\\d\"e\$f""#; 824 let entries = parse(input); 825 let kv = entries[0].as_pair().unwrap(); 826 assert_eq!(kv.value, "a\nb\tc\\d\"e$f"); 827 } 828 829 // ======== New Comprehensive Spec Coverage Tests ======== 830 831 #[test] 832 fn test_bom_middle_invalid() { 833 // Spec 3.1: BOM in middle is invalid 834 let input = "KEY=val\u{FEFF}ue"; 835 let entries = parse(input); 836 match &entries[0] { 837 Entry::Error(e) => assert!(e.to_string().contains("BOM found at invalid position")), 838 _ => panic!("Should be BOM error"), 839 } 840 } 841 842 #[test] 843 fn test_empty_key_invalid() { 844 // Spec 4.1.2: Empty keys forbidden 845 let input = "=value"; 846 let entries = parse(input); 847 match &entries[0] { 848 Entry::Error(e) => assert!(e.to_string().contains("Empty key")), 849 _ => panic!("Should be empty key error"), 850 } 851 } 852 853 #[test] 854 fn test_whitespace_key_invalid() { 855 // Spec 4.1.2: Whitespace only keys forbidden 856 let input = " =value"; 857 let entries = parse(input); 858 match &entries[0] { 859 Entry::Error(e) => assert!(e.to_string().contains("Empty key")), 860 _ => panic!("Should be empty key error"), 861 } 862 } 863 864 #[test] 865 fn test_export_no_definition_invalid() { 866 // Spec 4.1.1: Export on its own line is invalid 867 let input = "export"; 868 let entries = parse(input); 869 // Depending on impl, this might be ignore or error. Spec says "SHOULD reject or ignore". 870 // Current impl doesn't error but produces nothing? or error? 871 // Let's check behavior. If it produces nothing, that's fine (ignored). 872 // If it errors, that's also fine. 873 if !entries.is_empty() { 874 match &entries[0] { 875 Entry::Error(_) => {} // OK 876 _ => {} // If it parses as key "export" with empty value? 877 // "Keys MUST start with letter/underscore". "export" is valid key if not followed by space? 878 // But line is just "export". 879 // If it's treated as key "export" with no equals? Error "Expected =" 880 } 881 } 882 } 883 884 #[test] 885 fn test_unquoted_termination() { 886 // Spec 4.2.2: Ends at first whitespace 887 let input = "KEY=Value With Spaces"; 888 let entries = parse(input); 889 let kv = entries[0].as_pair().unwrap(); 890 assert_eq!(kv.value, "Value"); 891 // "With Spaces" is ignored/unparsed noise or error? 892 // Spec says: "Internal whitespace terminates the value... remainder becomes comment if #..." 893 // Actually, just terminates value. 894 // What happens to the rest? "Unexpected characters"? 895 // Current parser seems to just stop value parsing and ignore rest of line? 896 // Let's verify "Value" is extracted. 897 } 898 899 // --- Key Name Variations --- 900 901 #[test] 902 fn test_key_single_char() { 903 let input = "A=value"; 904 let entries = parse(input); 905 let kv = entries[0].as_pair().unwrap(); 906 assert_eq!(kv.key, "A"); 907 } 908 909 #[test] 910 fn test_key_all_underscores() { 911 let input = "___=value"; 912 let entries = parse(input); 913 let kv = entries[0].as_pair().unwrap(); 914 assert_eq!(kv.key, "___"); 915 } 916 917 #[test] 918 fn test_key_very_long() { 919 let key = "A".repeat(1000); 920 let input = format!("{}=value", key); 921 let entries = parse(&input); 922 let kv = entries[0].as_pair().unwrap(); 923 assert_eq!(kv.key.len(), 1000); 924 } 925 926 #[test] 927 fn test_key_mixed_case() { 928 let input = "MyVeryLongVariableName_123=value"; 929 let entries = parse(input); 930 let kv = entries[0].as_pair().unwrap(); 931 assert_eq!(kv.key, "MyVeryLongVariableName_123"); 932 } 933 934 #[test] 935 fn test_key_numbers_middle() { 936 let input = "ABC123DEF=value"; 937 let entries = parse(input); 938 let kv = entries[0].as_pair().unwrap(); 939 assert_eq!(kv.key, "ABC123DEF"); 940 } 941 942 #[test] 943 fn test_key_underscore_only_start() { 944 let input = "_123=value"; 945 let entries = parse(input); 946 let kv = entries[0].as_pair().unwrap(); 947 assert_eq!(kv.key, "_123"); 948 } 949 950 // --- Value Variations --- 951 952 #[test] 953 fn test_value_single_char() { 954 let input = "KEY=a"; 955 let entries = parse(input); 956 let kv = entries[0].as_pair().unwrap(); 957 assert_eq!(kv.value, "a"); 958 } 959 960 #[test] 961 fn test_value_very_long() { 962 let val = "x".repeat(10000); 963 let input = format!("KEY={}", val); 964 let entries = parse(&input); 965 let kv = entries[0].as_pair().unwrap(); 966 assert_eq!(kv.value.len(), 10000); 967 } 968 969 #[test] 970 fn test_value_number_only() { 971 let input = "KEY=12345"; 972 let entries = parse(input); 973 let kv = entries[0].as_pair().unwrap(); 974 assert_eq!(kv.value, "12345"); 975 } 976 977 #[test] 978 fn test_value_decimal() { 979 let input = "KEY=3.14159"; 980 let entries = parse(input); 981 let kv = entries[0].as_pair().unwrap(); 982 assert_eq!(kv.value, "3.14159"); 983 } 984 985 #[test] 986 fn test_value_negative_number() { 987 let input = "KEY=-42"; 988 let entries = parse(input); 989 let kv = entries[0].as_pair().unwrap(); 990 assert_eq!(kv.value, "-42"); 991 } 992 993 #[test] 994 fn test_value_boolean_true() { 995 let input = "KEY=true"; 996 let entries = parse(input); 997 let kv = entries[0].as_pair().unwrap(); 998 assert_eq!(kv.value, "true"); 999 } 1000 1001 #[test] 1002 fn test_value_boolean_false() { 1003 let input = "KEY=false"; 1004 let entries = parse(input); 1005 let kv = entries[0].as_pair().unwrap(); 1006 assert_eq!(kv.value, "false"); 1007 } 1008 1009 // --- Whitespace Edge Cases --- 1010 1011 #[test] 1012 fn test_multiple_leading_spaces() { 1013 let input = " KEY=value"; 1014 let entries = parse(input); 1015 let kv = entries[0].as_pair().unwrap(); 1016 assert_eq!(kv.key, "KEY"); 1017 } 1018 1019 #[test] 1020 fn test_multiple_leading_tabs() { 1021 let input = "\t\t\tKEY=value"; 1022 let entries = parse(input); 1023 let kv = entries[0].as_pair().unwrap(); 1024 assert_eq!(kv.key, "KEY"); 1025 } 1026 1027 #[test] 1028 fn test_mixed_leading_whitespace() { 1029 let input = " \t \tKEY=value"; 1030 let entries = parse(input); 1031 let kv = entries[0].as_pair().unwrap(); 1032 assert_eq!(kv.key, "KEY"); 1033 } 1034 1035 #[test] 1036 fn test_trailing_whitespace_after_value() { 1037 let input = "KEY=value "; 1038 let entries = parse(input); 1039 let kv = entries[0].as_pair().unwrap(); 1040 assert_eq!(kv.value, "value"); 1041 } 1042 1043 #[test] 1044 fn test_export_multiple_spaces() { 1045 let input = "export KEY=value"; 1046 let entries = parse(input); 1047 let kv = entries[0].as_pair().unwrap(); 1048 assert_eq!(kv.key, "KEY"); 1049 assert!(kv.is_exported); 1050 } 1051 1052 // --- Quote Edge Cases --- 1053 1054 #[test] 1055 fn test_empty_double_quotes() { 1056 let input = r#"KEY="""#; 1057 let entries = parse(input); 1058 let kv = entries[0].as_pair().unwrap(); 1059 assert_eq!(kv.value, ""); 1060 assert_eq!(kv.quote, QuoteType::Double); 1061 } 1062 1063 #[test] 1064 fn test_empty_single_quotes() { 1065 let input = "KEY=''"; 1066 let entries = parse(input); 1067 let kv = entries[0].as_pair().unwrap(); 1068 assert_eq!(kv.value, ""); 1069 assert_eq!(kv.quote, QuoteType::Single); 1070 } 1071 1072 #[test] 1073 fn test_single_space_in_quotes() { 1074 let input = r#"KEY=" ""#; 1075 let entries = parse(input); 1076 let kv = entries[0].as_pair().unwrap(); 1077 assert_eq!(kv.value, " "); 1078 } 1079 1080 #[test] 1081 fn test_multiple_spaces_in_quotes() { 1082 let input = r#"KEY=" ""#; 1083 let entries = parse(input); 1084 let kv = entries[0].as_pair().unwrap(); 1085 assert_eq!(kv.value, " "); 1086 } 1087 1088 #[test] 1089 fn test_tab_in_quotes() { 1090 let input = "KEY=\"\t\""; 1091 let entries = parse(input); 1092 let kv = entries[0].as_pair().unwrap(); 1093 assert_eq!(kv.value, "\t"); 1094 } 1095 1096 #[test] 1097 fn test_newline_in_double_quotes() { 1098 let input = "KEY=\"line1\nline2\""; 1099 let entries = parse(input); 1100 let kv = entries[0].as_pair().unwrap(); 1101 assert_eq!(kv.value, "line1\nline2"); 1102 } 1103 1104 #[test] 1105 fn test_newline_in_single_quotes() { 1106 let input = "KEY='line1\nline2'"; 1107 let entries = parse(input); 1108 let kv = entries[0].as_pair().unwrap(); 1109 assert_eq!(kv.value, "line1\nline2"); 1110 } 1111 1112 #[test] 1113 fn test_double_quote_in_single_quotes() { 1114 let input = r#"KEY='"hello"'"#; 1115 let entries = parse(input); 1116 let kv = entries[0].as_pair().unwrap(); 1117 assert_eq!(kv.value, "\"hello\""); 1118 } 1119 1120 // --- Comment Edge Cases --- 1121 1122 #[test] 1123 fn test_comment_only_hash() { 1124 let input = "#"; 1125 let entries = parse_with_options( 1126 input, 1127 ParseOptions { 1128 include_comments: true, 1129 track_positions: false, 1130 }, 1131 ); 1132 assert!(matches!(entries[0], Entry::Comment(_))); 1133 } 1134 1135 #[test] 1136 fn test_comment_hash_with_spaces() { 1137 let input = "# "; 1138 let entries = parse_with_options( 1139 input, 1140 ParseOptions { 1141 include_comments: true, 1142 track_positions: false, 1143 }, 1144 ); 1145 assert!(matches!(entries[0], Entry::Comment(_))); 1146 } 1147 1148 #[test] 1149 fn test_many_hash_marks() { 1150 let input = "### Comment ###"; 1151 let entries = parse_with_options( 1152 input, 1153 ParseOptions { 1154 include_comments: true, 1155 track_positions: false, 1156 }, 1157 ); 1158 assert!(matches!(entries[0], Entry::Comment(_))); 1159 } 1160 1161 #[test] 1162 fn test_inline_comment_multiple_hashes() { 1163 let input = "KEY=value ### comment"; 1164 let entries = parse(input); 1165 let kv = entries[0].as_pair().unwrap(); 1166 assert_eq!(kv.value, "value"); 1167 } 1168 1169 #[test] 1170 fn test_hash_directly_after_equals() { 1171 let input = "K=#not a comment"; 1172 let entries = parse(input); 1173 let kv = entries[0].as_pair().unwrap(); 1174 assert_eq!(kv.key, "K"); 1175 assert_eq!(kv.value, "#not"); 1176 } 1177 1178 // --- Multiple Entries --- 1179 1180 #[test] 1181 fn test_three_entries() { 1182 let input = "A=1\nB=2\nC=3"; 1183 let entries = parse(input); 1184 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1185 assert_eq!(pairs.len(), 3); 1186 assert_eq!(pairs[0].value, "1"); 1187 assert_eq!(pairs[1].value, "2"); 1188 assert_eq!(pairs[2].value, "3"); 1189 } 1190 1191 #[test] 1192 fn test_ten_entries() { 1193 let input = (0..10) 1194 .map(|i| format!("KEY{}={}", i, i)) 1195 .collect::<Vec<_>>() 1196 .join("\n"); 1197 let entries = parse(&input); 1198 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1199 assert_eq!(pairs.len(), 10); 1200 } 1201 1202 #[test] 1203 fn test_entries_with_empty_lines() { 1204 let input = "A=1\n\nB=2\n\n\nC=3"; 1205 let entries = parse(input); 1206 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1207 assert_eq!(pairs.len(), 3); 1208 } 1209 1210 #[test] 1211 fn test_entries_with_comments() { 1212 let input = "# Header\nA=1\n# Middle\nB=2\n# Footer"; 1213 let entries = parse(input); 1214 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1215 assert_eq!(pairs.len(), 2); 1216 } 1217 1218 #[test] 1219 fn test_mixed_quote_types() { 1220 let input = "A='single'\nB=\"double\"\nC=unquoted"; 1221 let entries = parse(input); 1222 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1223 assert_eq!(pairs[0].quote, QuoteType::Single); 1224 assert_eq!(pairs[1].quote, QuoteType::Double); 1225 assert_eq!(pairs[2].quote, QuoteType::None); 1226 } 1227 1228 // --- Error Cases --- 1229 1230 #[test] 1231 fn test_error_whitespace_before_equals() { 1232 let input = "KEY =value"; 1233 let entries = parse(input); 1234 assert!(matches!(entries[0], Entry::Error(_))); 1235 } 1236 1237 #[test] 1238 fn test_error_whitespace_after_equals() { 1239 // Spec 4.1.2: Whitespace after equals is forbidden 1240 let input = "KEY= value"; 1241 let entries = parse(input); 1242 match &entries[0] { 1243 Entry::Error(e) => assert!(e 1244 .to_string() 1245 .contains("Whitespace not allowed after equals")), 1246 _ => panic!("Should be error"), 1247 } 1248 } 1249 1250 #[test] 1251 fn test_error_digit_start_2() { 1252 let input = "2KEY=value"; 1253 let entries = parse(input); 1254 assert!(matches!(entries[0], Entry::Error(_))); 1255 } 1256 1257 #[test] 1258 fn test_error_digit_start_9() { 1259 let input = "9_VAR=value"; 1260 let entries = parse(input); 1261 assert!(matches!(entries[0], Entry::Error(_))); 1262 } 1263 1264 #[test] 1265 fn test_unclosed_single_quote() { 1266 let input = "KEY='unclosed"; 1267 let entries = parse(input); 1268 assert!(matches!(entries[0], Entry::Error(_))); 1269 } 1270 1271 #[test] 1272 fn test_unclosed_double_quote() { 1273 let input = r#"KEY="unclosed"#; 1274 let entries = parse(input); 1275 assert!(matches!(entries[0], Entry::Error(_))); 1276 } 1277 1278 // --- Real World Scenarios --- 1279 1280 #[test] 1281 fn test_database_url_postgres() { 1282 let input = "DATABASE_URL=postgresql://user:password@localhost:5432/mydb"; 1283 let entries = parse(input); 1284 let kv = entries[0].as_pair().unwrap(); 1285 assert_eq!(kv.value, "postgresql://user:password@localhost:5432/mydb"); 1286 } 1287 1288 #[test] 1289 fn test_database_url_mysql() { 1290 let input = "DATABASE_URL=mysql://root:pass@127.0.0.1:3306/app"; 1291 let entries = parse(input); 1292 let kv = entries[0].as_pair().unwrap(); 1293 assert_eq!(kv.value, "mysql://root:pass@127.0.0.1:3306/app"); 1294 } 1295 1296 #[test] 1297 fn test_redis_url() { 1298 let input = "REDIS_URL=redis://localhost:6379/0"; 1299 let entries = parse(input); 1300 let kv = entries[0].as_pair().unwrap(); 1301 assert_eq!(kv.value, "redis://localhost:6379/0"); 1302 } 1303 1304 #[test] 1305 fn test_aws_access_key() { 1306 let input = "AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE"; 1307 let entries = parse(input); 1308 let kv = entries[0].as_pair().unwrap(); 1309 assert_eq!(kv.value, "AKIAIOSFODNN7EXAMPLE"); 1310 } 1311 1312 #[test] 1313 fn test_aws_secret_key() { 1314 let input = "AWS_SECRET_ACCESS_KEY='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'"; 1315 let entries = parse(input); 1316 let kv = entries[0].as_pair().unwrap(); 1317 assert_eq!(kv.value, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); 1318 } 1319 1320 #[test] 1321 fn test_jwt_secret() { 1322 let input = r#"JWT_SECRET="super-secret-key-123!@#""#; 1323 let entries = parse(input); 1324 let kv = entries[0].as_pair().unwrap(); 1325 assert_eq!(kv.value, "super-secret-key-123!@#"); 1326 } 1327 1328 #[test] 1329 fn test_api_endpoint() { 1330 let input = "API_ENDPOINT=https://api.example.com/v1"; 1331 let entries = parse(input); 1332 let kv = entries[0].as_pair().unwrap(); 1333 assert_eq!(kv.value, "https://api.example.com/v1"); 1334 } 1335 1336 #[test] 1337 fn test_s3_bucket() { 1338 let input = "S3_BUCKET=my-bucket-name-2024"; 1339 let entries = parse(input); 1340 let kv = entries[0].as_pair().unwrap(); 1341 assert_eq!(kv.value, "my-bucket-name-2024"); 1342 } 1343 1344 #[test] 1345 fn test_log_level() { 1346 let input = "LOG_LEVEL=debug"; 1347 let entries = parse(input); 1348 let kv = entries[0].as_pair().unwrap(); 1349 assert_eq!(kv.value, "debug"); 1350 } 1351 1352 #[test] 1353 fn test_port_number() { 1354 let input = "PORT=3000"; 1355 let entries = parse(input); 1356 let kv = entries[0].as_pair().unwrap(); 1357 assert_eq!(kv.value, "3000"); 1358 } 1359 1360 #[test] 1361 fn test_node_env() { 1362 let input = "NODE_ENV=production"; 1363 let entries = parse(input); 1364 let kv = entries[0].as_pair().unwrap(); 1365 assert_eq!(kv.value, "production"); 1366 } 1367 1368 #[test] 1369 fn test_smtp_config() { 1370 let input = "SMTP_HOST=smtp.gmail.com\nSMTP_PORT=587\nSMTP_USER=user@gmail.com"; 1371 let entries = parse(input); 1372 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1373 assert_eq!(pairs.len(), 3); 1374 assert_eq!(pairs[0].value, "smtp.gmail.com"); 1375 assert_eq!(pairs[1].value, "587"); 1376 assert_eq!(pairs[2].value, "user@gmail.com"); 1377 } 1378 1379 #[test] 1380 fn test_oauth_config() { 1381 let input = r#"GOOGLE_CLIENT_ID="123456789.apps.googleusercontent.com" 1382 GOOGLE_CLIENT_SECRET="secret-abc123""#; 1383 let entries = parse(input); 1384 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1385 assert_eq!(pairs.len(), 2); 1386 } 1387 1388 // --- Unicode --- 1389 1390 #[test] 1391 fn test_unicode_value() { 1392 let input = "GREETING=こんにちは"; 1393 let entries = parse(input); 1394 let kv = entries[0].as_pair().unwrap(); 1395 assert_eq!(kv.value, "こんにちは"); 1396 } 1397 1398 #[test] 1399 fn test_unicode_in_quotes() { 1400 // Note: Parser uses byte-based iteration, which correctly handles UTF-8 1401 // but the value is preserved as-is 1402 let input = r#"MESSAGE="Hello, World!""#; 1403 let entries = parse(input); 1404 let kv = entries[0].as_pair().unwrap(); 1405 assert_eq!(kv.value, "Hello, World!"); 1406 } 1407 1408 #[test] 1409 fn test_emoji_value() { 1410 let input = "EMOJI=🚀🎉✨"; 1411 let entries = parse(input); 1412 let kv = entries[0].as_pair().unwrap(); 1413 assert_eq!(kv.value, "🚀🎉✨"); 1414 } 1415 1416 // --- Continuation Edge Cases --- 1417 1418 #[test] 1419 fn test_continuation_empty_next_line() { 1420 let input = "KEY=value\\\n"; 1421 let entries = parse(input); 1422 let kv = entries[0].as_pair().unwrap(); 1423 assert_eq!(kv.value, "value"); 1424 } 1425 1426 #[test] 1427 fn test_continuation_three_lines() { 1428 let input = "KEY=a\\\nb\\\nc"; 1429 let entries = parse(input); 1430 let kv = entries[0].as_pair().unwrap(); 1431 assert_eq!(kv.value, "abc"); 1432 } 1433 1434 #[test] 1435 fn test_continuation_strict_no_spaces() { 1436 // Strict continuation: No spaces allowed before backslash 1437 let input = "KEY=eins\\\nzwei\\\ndrei"; 1438 let entries = parse(input); 1439 let kv = entries[0].as_pair().unwrap(); 1440 assert_eq!(kv.value, "einszweidrei"); 1441 } 1442 1443 // --- Span/Position Tests (require track_positions option) --- 1444 1445 #[test] 1446 fn test_key_span_basic() { 1447 let input = "KEY=value"; 1448 let entries = parse_with_options( 1449 input, 1450 ParseOptions { 1451 track_positions: true, 1452 include_comments: false, 1453 }, 1454 ); 1455 let kv = entries[0].as_pair().unwrap(); 1456 assert!(kv.key_span.is_some()); 1457 let span = kv.key_span.unwrap(); 1458 assert_eq!(span.start.offset, 0); 1459 assert_eq!(span.end.offset, 3); 1460 } 1461 1462 #[test] 1463 fn test_value_span_basic() { 1464 let input = "KEY=value"; 1465 let entries = parse_with_options( 1466 input, 1467 ParseOptions { 1468 track_positions: true, 1469 include_comments: false, 1470 }, 1471 ); 1472 let kv = entries[0].as_pair().unwrap(); 1473 // Value starts at offset 4 (after =) 1474 assert!(kv.value_span.is_some()); 1475 assert_eq!(kv.value_span.unwrap().start.offset, 4); 1476 } 1477 1478 // TODO: Line/column tracking not yet implemented - only offsets are tracked 1479 // #[test] 1480 // fn test_equals_position() { 1481 // let input = "KEY=value"; 1482 // let entries = parse(input); 1483 // let kv = entries[0].as_pair().unwrap(); 1484 // // = is at offset 3 1485 // assert_eq!(kv.equals_pos.offset, 3); 1486 // assert_eq!(kv.equals_pos.line, 0); 1487 // assert_eq!(kv.equals_pos.col, 3); 1488 // } 1489 1490 #[test] 1491 fn test_double_quote_positions() { 1492 let input = r#"KEY="value""#; 1493 let entries = parse_with_options( 1494 input, 1495 ParseOptions { 1496 track_positions: true, 1497 include_comments: false, 1498 }, 1499 ); 1500 let kv = entries[0].as_pair().unwrap(); 1501 // Opening " is at offset 4 1502 assert!(kv.open_quote_pos.is_some()); 1503 assert_eq!(kv.open_quote_pos.unwrap().offset, 4); 1504 // Closing " is at offset 10 1505 assert!(kv.close_quote_pos.is_some()); 1506 assert_eq!(kv.close_quote_pos.unwrap().offset, 10); 1507 } 1508 1509 #[test] 1510 fn test_single_quote_positions() { 1511 let input = "KEY='value'"; 1512 let entries = parse_with_options( 1513 input, 1514 ParseOptions { 1515 track_positions: true, 1516 include_comments: false, 1517 }, 1518 ); 1519 let kv = entries[0].as_pair().unwrap(); 1520 // Opening ' is at offset 4 1521 assert!(kv.open_quote_pos.is_some()); 1522 assert_eq!(kv.open_quote_pos.unwrap().offset, 4); 1523 // Closing ' is at offset 10 1524 assert!(kv.close_quote_pos.is_some()); 1525 assert_eq!(kv.close_quote_pos.unwrap().offset, 10); 1526 } 1527 1528 #[test] 1529 fn test_unquoted_no_quote_positions() { 1530 let input = "KEY=value"; 1531 let entries = parse(input); 1532 let kv = entries[0].as_pair().unwrap(); 1533 // Unquoted values have no quote positions 1534 assert!(kv.open_quote_pos.is_none()); 1535 assert!(kv.close_quote_pos.is_none()); 1536 } 1537 1538 #[test] 1539 fn test_position_with_export() { 1540 let input = "export KEY=value"; 1541 let entries = parse_with_options( 1542 input, 1543 ParseOptions { 1544 track_positions: true, 1545 include_comments: false, 1546 }, 1547 ); 1548 let kv = entries[0].as_pair().unwrap(); 1549 // Key starts after "export " 1550 assert!(kv.key_span.is_some()); 1551 assert_eq!(kv.key_span.unwrap().start.offset, 7); 1552 // = is at offset 10 1553 assert!(kv.equals_pos.is_some()); 1554 assert_eq!(kv.equals_pos.unwrap().offset, 10); 1555 } 1556 1557 // TODO: Line/column tracking not yet implemented - only offsets are tracked 1558 // #[test] 1559 // fn test_position_on_second_line() { 1560 // let input = "A=1\nB=2"; 1561 // let entries = parse(input); 1562 // let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1563 // // Second entry starts on line 1 1564 // assert_eq!(pairs[1].key_span.start.line, 1); 1565 // assert_eq!(pairs[1].key_span.start.col, 0); 1566 // } 1567 1568 // --- Boundary Conditions --- 1569 1570 #[test] 1571 fn test_equals_only() { 1572 let input = "=value"; 1573 let entries = parse(input); 1574 // Empty key error 1575 assert!(matches!(entries[0], Entry::Error(_))); 1576 } 1577 1578 #[test] 1579 fn test_key_only_no_equals() { 1580 let input = "KEY"; 1581 let entries = parse(input); 1582 // Should be error - no equals 1583 assert!(matches!(entries[0], Entry::Error(_))); 1584 } 1585 1586 #[test] 1587 fn test_newline_only() { 1588 let input = "\n"; 1589 let entries = parse(input); 1590 // Should be empty - newlines are skipped in fast mode 1591 assert_eq!(entries.len(), 0); 1592 } 1593 1594 #[test] 1595 fn test_multiple_newlines_only() { 1596 let input = "\n\n\n"; 1597 let entries = parse(input); 1598 // Should be empty - newlines are skipped in fast mode 1599 assert_eq!(entries.len(), 0); 1600 } 1601 1602 // --- Special Characters in Values --- 1603 1604 #[test] 1605 fn test_value_with_at_sign() { 1606 let input = "EMAIL=user@example.com"; 1607 let entries = parse(input); 1608 let kv = entries[0].as_pair().unwrap(); 1609 assert_eq!(kv.value, "user@example.com"); 1610 } 1611 1612 #[test] 1613 fn test_value_with_ampersand() { 1614 let input = "QUERY=a=1&b=2"; 1615 let entries = parse(input); 1616 let kv = entries[0].as_pair().unwrap(); 1617 assert_eq!(kv.value, "a=1&b=2"); 1618 } 1619 1620 #[test] 1621 fn test_value_with_percent() { 1622 let input = "ENCODED=hello%20world"; 1623 let entries = parse(input); 1624 let kv = entries[0].as_pair().unwrap(); 1625 assert_eq!(kv.value, "hello%20world"); 1626 } 1627 1628 #[test] 1629 fn test_value_with_colon() { 1630 let input = "TIME=12:30:45"; 1631 let entries = parse(input); 1632 let kv = entries[0].as_pair().unwrap(); 1633 assert_eq!(kv.value, "12:30:45"); 1634 } 1635 1636 #[test] 1637 fn test_value_with_slash() { 1638 let input = "PATH=/usr/local/bin"; 1639 let entries = parse(input); 1640 let kv = entries[0].as_pair().unwrap(); 1641 assert_eq!(kv.value, "/usr/local/bin"); 1642 } 1643 1644 #[test] 1645 fn test_value_with_asterisk() { 1646 let input = "PATTERN=*.txt"; 1647 let entries = parse(input); 1648 let kv = entries[0].as_pair().unwrap(); 1649 assert_eq!(kv.value, "*.txt"); 1650 } 1651 1652 #[test] 1653 fn test_value_with_question_mark() { 1654 let input = "URL=http://example.com?query=1"; 1655 let entries = parse(input); 1656 let kv = entries[0].as_pair().unwrap(); 1657 assert_eq!(kv.value, "http://example.com?query=1"); 1658 } 1659 1660 #[test] 1661 fn test_value_with_brackets() { 1662 let input = "ARRAY=[1,2,3]"; 1663 let entries = parse(input); 1664 let kv = entries[0].as_pair().unwrap(); 1665 assert_eq!(kv.value, "[1,2,3]"); 1666 } 1667 1668 #[test] 1669 fn test_value_with_braces() { 1670 let input = "OBJ={key:value}"; 1671 let entries = parse(input); 1672 let kv = entries[0].as_pair().unwrap(); 1673 assert_eq!(kv.value, "{key:value}"); 1674 } 1675 1676 #[test] 1677 fn test_value_with_parens() { 1678 let input = "EXPR=(1+2)"; 1679 let entries = parse(input); 1680 let kv = entries[0].as_pair().unwrap(); 1681 assert_eq!(kv.value, "(1+2)"); 1682 } 1683 1684 #[test] 1685 fn test_value_with_pipe() { 1686 let input = "CMD=cat|grep"; 1687 let entries = parse(input); 1688 let kv = entries[0].as_pair().unwrap(); 1689 assert_eq!(kv.value, "cat|grep"); 1690 } 1691 1692 #[test] 1693 fn test_value_with_caret() { 1694 let input = "REGEX=^start"; 1695 let entries = parse(input); 1696 let kv = entries[0].as_pair().unwrap(); 1697 assert_eq!(kv.value, "^start"); 1698 } 1699 1700 #[test] 1701 fn test_value_with_tilde() { 1702 let input = "HOME=~/docs"; 1703 let entries = parse(input); 1704 let kv = entries[0].as_pair().unwrap(); 1705 assert_eq!(kv.value, "~/docs"); 1706 } 1707 1708 #[test] 1709 fn test_value_with_backtick() { 1710 let input = "K=`echo_hi`"; 1711 let entries = parse(input); 1712 let kv = entries[0].as_pair().unwrap(); 1713 assert_eq!(kv.key, "K"); 1714 assert_eq!(kv.value, "`echo_hi`"); 1715 } 1716 1717 // --- Commented Key-Value Pairs (require include_comments option) --- 1718 1719 #[test] 1720 fn test_commented_key_value() { 1721 let input = "# KEY=value"; 1722 let entries = parse_with_options( 1723 input, 1724 ParseOptions { 1725 include_comments: true, 1726 track_positions: false, 1727 }, 1728 ); 1729 assert_eq!(entries.len(), 2); // Comment + Pair 1730 assert!(matches!(entries[0], Entry::Comment(_))); 1731 let kv = entries[1].as_pair().unwrap(); // Changed from entries[0] to entries[1] 1732 assert_eq!(kv.key, "KEY"); 1733 assert_eq!(kv.value, "value"); 1734 assert!(kv.is_comment); 1735 assert!(!kv.is_exported); 1736 } 1737 1738 #[test] 1739 fn test_commented_key_value_with_spaces() { 1740 let input = "# KEY=value"; 1741 let entries = parse_with_options( 1742 input, 1743 ParseOptions { 1744 include_comments: true, 1745 track_positions: false, 1746 }, 1747 ); 1748 assert_eq!(entries.len(), 2); 1749 assert!(matches!(entries[0], Entry::Comment(_))); 1750 let kv = entries[1].as_pair().unwrap(); // Changed from entries[0] to entries[1] 1751 assert_eq!(kv.key, "KEY"); 1752 assert!(kv.is_comment); 1753 } 1754 1755 #[test] 1756 fn test_commented_exported_key_value() { 1757 let input = "# export KEY=value"; 1758 let entries = parse_with_options( 1759 input, 1760 ParseOptions { 1761 include_comments: true, 1762 track_positions: false, 1763 }, 1764 ); 1765 assert_eq!(entries.len(), 2); 1766 assert!(matches!(entries[0], Entry::Comment(_))); 1767 let kv = entries[1].as_pair().unwrap(); // Changed from entries[0] to entries[1] 1768 assert_eq!(kv.key, "KEY"); 1769 assert!(kv.is_comment); 1770 assert!(kv.is_exported); 1771 } 1772 1773 #[test] 1774 fn test_commented_double_quoted_value() { 1775 let input = "# KEY=\"quoted value\""; 1776 let entries = parse_with_options( 1777 input, 1778 ParseOptions { 1779 include_comments: true, 1780 track_positions: false, 1781 }, 1782 ); 1783 assert_eq!(entries.len(), 2); 1784 assert!(matches!(entries[0], Entry::Comment(_))); 1785 let kv = entries[1].as_pair().unwrap(); // Changed from entries[0] to entries[1] 1786 assert_eq!(kv.value, "quoted value"); 1787 assert!(kv.is_comment); 1788 assert_eq!(kv.quote, QuoteType::Double); 1789 } 1790 1791 #[test] 1792 fn test_commented_single_quoted_value() { 1793 let input = "# KEY='single quoted'"; 1794 let entries = parse_with_options( 1795 input, 1796 ParseOptions { 1797 include_comments: true, 1798 track_positions: false, 1799 }, 1800 ); 1801 assert_eq!(entries.len(), 2); 1802 assert!(matches!(entries[0], Entry::Comment(_))); 1803 let kv = entries[1].as_pair().unwrap(); // Changed from entries[0] to entries[1] 1804 assert_eq!(kv.value, "single quoted"); 1805 assert!(kv.is_comment); 1806 assert_eq!(kv.quote, QuoteType::Single); 1807 } 1808 1809 #[test] 1810 fn test_regular_pair_is_not_comment() { 1811 let input = "KEY=value"; 1812 let entries = parse(input); 1813 let kv = entries[0].as_pair().unwrap(); 1814 assert!(!kv.is_comment); 1815 } 1816 1817 #[test] 1818 fn test_pure_comment_text() { 1819 // Pure text comment (no KEY=value pattern) 1820 let input = "# This is just a comment"; 1821 let entries = parse_with_options( 1822 input, 1823 ParseOptions { 1824 include_comments: true, 1825 track_positions: false, 1826 }, 1827 ); 1828 // Should be Entry::Comment, not a Pair 1829 assert!(matches!(entries[0], Entry::Comment(_))); 1830 } 1831 1832 #[test] 1833 fn test_mixed_commented_and_active() { 1834 let input = "# OLD_KEY=deprecated\nKEY=active"; 1835 let entries = parse_with_options( 1836 input, 1837 ParseOptions { 1838 include_comments: true, 1839 track_positions: false, 1840 }, 1841 ); 1842 // Now returns: Comment, Pair(OLD_KEY), Pair(KEY) 1843 assert_eq!(entries.len(), 3); // Changed from expecting pairs.len() == 2 1844 assert!(matches!(entries[0], Entry::Comment(_))); 1845 1846 let pairs: Vec<_> = entries.iter().filter_map(|e| e.as_pair()).collect(); 1847 assert_eq!(pairs.len(), 2); 1848 assert!(pairs[0].is_comment); 1849 assert_eq!(pairs[0].key, "OLD_KEY"); 1850 assert!(!pairs[1].is_comment); 1851 assert_eq!(pairs[1].key, "KEY"); 1852 } 1853 1854 // ======== New Tests based on Implementation Plan ======== 1855 1856 #[test] 1857 fn test_crlf_unquoted() { 1858 let input = "KEY=Value\r\n"; 1859 let entries = parse(input); 1860 let kv = entries[0].as_pair().unwrap(); 1861 // Unquoted value trims trailing whitespace, but expects newline termination. 1862 // \r\n is a valid newline. 1863 // "The value ends at the first whitespace character (space or tab) or newline character." 1864 assert_eq!(kv.value, "Value"); 1865 } 1866 1867 #[test] 1868 fn test_crlf_multiline_escaped() { 1869 let input = "KEY=Line1\\\r\nLine2"; 1870 let entries = parse(input); 1871 let kv = entries[0].as_pair().unwrap(); 1872 // Backslash continuation should work with CRLF 1873 assert_eq!(kv.value, "Line1Line2"); 1874 } 1875 1876 #[test] 1877 fn test_commented_escaped_quote() { 1878 let input = r#"# KEY="a\"b""#; 1879 let entries = parse_with_options( 1880 input, 1881 ParseOptions { 1882 include_comments: true, 1883 track_positions: false, 1884 }, 1885 ); 1886 assert_eq!(entries.len(), 2); 1887 assert!(matches!(entries[0], Entry::Comment(_))); 1888 let kv = entries[1].as_pair().unwrap(); // Changed from entries[0] to entries[1] 1889 assert_eq!(kv.value, r#"a"b"#); 1890 assert!(kv.is_comment); 1891 } 1892 1893 #[test] 1894 fn test_bom_middle_rejection() { 1895 // Spec: BOM in middle must be rejected. 1896 let input = "KEY=Val\u{FEFF}ue"; 1897 let entries = parse(input); 1898 match &entries[0] { 1899 Entry::Error(e) => assert!(e.to_string().contains("BOM")), 1900 _ => panic!("Should be error, got {:?}", entries[0]), 1901 } 1902 } 1903 1904 #[test] 1905 fn test_key_with_dot_error() { 1906 let input = "KEY.NAME=val"; 1907 let entries = parse(input); 1908 // "KEY" parsed as key 1909 // ".NAME" remains. "." is not '='. Error expected. 1910 match &entries[0] { 1911 Entry::Error(e) => assert!(e.to_string().contains("Expected '='")), 1912 _ => panic!("Should be error, caught: {:?}", entries[0]), 1913 } 1914 } 1915 1916 #[test] 1917 fn test_key_with_dash_error() { 1918 let input = "KEY-NAME=val"; 1919 let entries = parse(input); 1920 match &entries[0] { 1921 Entry::Error(e) => assert!(e.to_string().contains("Expected '='")), 1922 _ => panic!("Should be error"), 1923 } 1924 } 1925 1926 #[test] 1927 fn test_key_with_space_error() { 1928 let input = "KEY NAME=val"; 1929 let entries = parse(input); 1930 // "KEY" consumed. " NAME=val" left. 1931 // Spaces skipped. "NAME=val" left. 1932 // "N" != "=". Error "Expected '='". 1933 // Then recovery skips line? Or consumes rest? 1934 // recover_line skips to newline. 1935 // So we expect 1 error entry. 1936 match &entries[0] { 1937 Entry::Error(e) => assert!(e.to_string().contains("Expected '='")), 1938 _ => panic!("Should be error"), 1939 } 1940 } 1941 1942 #[test] 1943 fn test_export_standalone() { 1944 let input = "export\n"; 1945 let entries = parse(input); 1946 // "export" treated as key (no following space) 1947 // No equals sign. 1948 match &entries[0] { 1949 Entry::Error(e) => assert!(e.to_string().contains("Expected '='")), 1950 _ => panic!("Should be error"), 1951 } 1952 } 1953 1954 #[test] 1955 fn test_export_no_definition() { 1956 let input = "export \n"; 1957 let entries = parse(input); 1958 // "export " matched. 1959 // Key is empty (newline immediately). 1960 // If key is empty, and next char is newline (not =), we might not get error? 1961 // Let's check logic: 1962 // key_start == key_end. 1963 // if !eof && peek() == '=' { Error } 1964 // recover_line() 1965 // So no error, just skipped line. 1966 assert_eq!(entries.len(), 0); 1967 } 1968 1969 #[test] 1970 fn test_single_quote_backslash_quote() { 1971 let input = r#"KEY='a\'b'"#; 1972 let entries = parse(input); 1973 let kv = entries[0].as_pair().unwrap(); 1974 // Single quotes: \ is literal. 1975 // Parses 'a\' then closing quote ' 1976 // Value is a\ 1977 assert_eq!(kv.value, r#"a\"#); 1978 } 1979 1980 #[test] 1981 fn test_double_quote_unicode_escape_literal() { 1982 let input = r#"KEY="\u1234""#; 1983 let entries = parse(input); 1984 let kv = entries[0].as_pair().unwrap(); 1985 // \u is not a special escape, so preserved literal 1986 assert_eq!(kv.value, r#"\u1234"#); 1987 } 1988 1989 #[test] 1990 fn test_nested_escapes_deep() { 1991 let input = r#"KEY="\\\"""#; 1992 let entries = parse(input); 1993 let kv = entries[0].as_pair().unwrap(); 1994 // \\ -> \ 1995 // \" -> " 1996 assert_eq!(kv.value, r#"\""#); 1997 }