You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
16 KiB
496 lines
16 KiB
#!/usr/bin/env tclsh |
|
# MIME Module Test Suite |
|
# Tests for untested code paths in mime-1.7.1.tm |
|
|
|
package require Tcl 8.5 |
|
package require mime |
|
|
|
# Test counter |
|
set test_count 0 |
|
set test_passed 0 |
|
set test_failed 0 |
|
|
|
proc test {name body expected} { |
|
global test_count test_passed test_failed |
|
incr test_count |
|
|
|
if {[catch {uplevel 1 $body} result]} { |
|
puts "FAIL: $name" |
|
puts " Error: $result" |
|
incr test_failed |
|
return 0 |
|
} |
|
|
|
if {$result eq $expected} { |
|
puts "PASS: $name" |
|
incr test_passed |
|
return 1 |
|
} else { |
|
puts "FAIL: $name" |
|
puts " Expected: $expected" |
|
puts " Got: $result" |
|
incr test_failed |
|
return 0 |
|
} |
|
} |
|
|
|
proc test_error {name body error_pattern} { |
|
global test_count test_passed test_failed |
|
incr test_count |
|
|
|
if {[catch {uplevel 1 $body} result]} { |
|
if {[string match $error_pattern $result]} { |
|
puts "PASS: $name (error caught as expected)" |
|
incr test_passed |
|
return 1 |
|
} else { |
|
puts "FAIL: $name" |
|
puts " Expected error matching: $error_pattern" |
|
puts " Got error: $result" |
|
incr test_failed |
|
return 0 |
|
} |
|
} else { |
|
puts "FAIL: $name (no error raised)" |
|
puts " Expected error matching: $error_pattern" |
|
puts " Got result: $result" |
|
incr test_failed |
|
return 0 |
|
} |
|
} |
|
|
|
# ============================================================================ |
|
# Test 1: mime::finalize with -subordinates all |
|
# Tests the untested code path at line 1172 |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 1: mime::finalize with -subordinates all ===" |
|
|
|
test "finalize with -subordinates all (no parts)" { |
|
set token [mime::initialize -canonical "text/plain" -string "Hello World"] |
|
mime::finalize $token -subordinates all |
|
# If we get here without error, test passes |
|
expr {1} |
|
} 1 |
|
|
|
test "finalize with -subordinates all (with parts)" { |
|
set parent [mime::initialize -canonical "multipart/mixed" -parts [list]] |
|
|
|
# Finalize with all subordinates |
|
mime::finalize $parent -subordinates all |
|
expr {1} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 2: mime::finalize with -subordinates dynamic |
|
# Tests the dynamic subordinates path |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 2: mime::finalize with -subordinates dynamic ===" |
|
|
|
test "finalize with -subordinates dynamic" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
mime::finalize $token -subordinates dynamic |
|
expr {1} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 3: mime::finalize with -subordinates none |
|
# Tests the none subordinates path |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 3: mime::finalize with -subordinates none ===" |
|
|
|
test "finalize with -subordinates none" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
mime::finalize $token -subordinates none |
|
expr {1} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 4: mime::finalize with invalid -subordinates |
|
# Tests error handling |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 4: mime::finalize error handling ===" |
|
|
|
test_error "finalize with invalid -subordinates" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
mime::finalize $token -subordinates invalid |
|
} "*unknown value for -subordinates*" |
|
|
|
# ============================================================================ |
|
# Test 5: mime::setheader with new key (untested path at line 1517) |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 5: mime::setheader with new key ===" |
|
|
|
test "setheader adding new header key" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
mime::setheader $token X-Custom-Header "custom value" |
|
set result [mime::getheader $token X-Custom-Header] |
|
mime::finalize $token |
|
expr {[string match "*custom value*" $result]} |
|
} 1 |
|
|
|
test "setheader delete non-existent key error" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
set result [catch {mime::setheader $token X-NonExistent "" -mode delete} err] |
|
mime::finalize $token |
|
expr {$result == 1} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 6: mime::encodingasciiP with non-ASCII text (untested path at line 2187) |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 6: mime::encodingasciiP and encoding detection ===" |
|
|
|
test "encodingasciiP with pure ASCII" { |
|
mime::encodingasciiP "Hello World" |
|
} 1 |
|
|
|
test "encodingasciiP with non-ASCII" { |
|
mime::encodingasciiP "Héllo Wörld" |
|
} 0 |
|
|
|
test "encodingasciiP with soft newlines" { |
|
mime::encodingasciiP "Hello=\nWorld" |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 7: mime::qp_decode with encoded characters (untested path at line 2455) |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 7: mime::qp_decode ===" |
|
|
|
test "qp_decode with encoded equals" { |
|
set encoded "Hello=3DWorld" |
|
set result [mime::qp_decode $encoded] |
|
string match "*Hello*World*" $result |
|
} 1 |
|
|
|
test "qp_decode with soft newline" { |
|
set encoded "Hello=\nWorld" |
|
set result [mime::qp_decode $encoded] |
|
string match "*HelloWorld*" $result |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 8: mime::parseaddress basic functionality |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 8: mime::parseaddress ===" |
|
|
|
test "parseaddress simple email" { |
|
set result [mime::parseaddress "user@example.com"] |
|
llength $result |
|
} 1 |
|
|
|
test "parseaddress with display name" { |
|
set result [mime::parseaddress "John Doe <john@example.com>"] |
|
llength $result |
|
} 1 |
|
|
|
test "parseaddress multiple addresses" { |
|
set result [mime::parseaddress "user1@example.com, user2@example.com"] |
|
llength $result |
|
} 2 |
|
|
|
test "parseaddress with comment" { |
|
set result [mime::parseaddress "user@example.com (comment)"] |
|
llength $result |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 9: mime::copymessage with different value types |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 9: mime::copymessage ===" |
|
|
|
test "copymessage with string content" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test content"] |
|
set tmpfile [file tempfile] |
|
set fd [open $tmpfile w+] |
|
mime::copymessage $token $fd |
|
close $fd |
|
file delete $tmpfile |
|
mime::finalize $token |
|
expr {1} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 10: mime::buildmessage |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 10: mime::buildmessage ===" |
|
|
|
test "buildmessage with string content" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test content"] |
|
set result [mime::buildmessage $token] |
|
mime::finalize $token |
|
# buildmessage includes headers, so just check it's not empty |
|
expr {[string length $result] > 0} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 11: mime::getproperty with various properties |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 11: mime::getproperty ===" |
|
|
|
test "getproperty content" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
set result [mime::getproperty $token content] |
|
mime::finalize $token |
|
expr {$result eq "text/plain"} |
|
} 1 |
|
|
|
test "getproperty encoding" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
set result [mime::getproperty $token encoding] |
|
mime::finalize $token |
|
expr {$result eq ""} |
|
} 1 |
|
|
|
test "getproperty size" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
set result [mime::getproperty $token size] |
|
mime::finalize $token |
|
expr {$result == 4} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 12: mime::initialize with different content types |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 12: mime::initialize ===" |
|
|
|
test "initialize with text/plain" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
set result [mime::getproperty $token content] |
|
mime::finalize $token |
|
expr {$result eq "text/plain"} |
|
} 1 |
|
|
|
test "initialize with text/html" { |
|
set token [mime::initialize -canonical "text/html" -string "<html></html>"] |
|
set result [mime::getproperty $token content] |
|
mime::finalize $token |
|
expr {$result eq "text/html"} |
|
} 1 |
|
|
|
test "initialize with application/octet-stream" { |
|
set token [mime::initialize -canonical "application/octet-stream" -string "binary"] |
|
set result [mime::getproperty $token content] |
|
mime::finalize $token |
|
expr {$result eq "application/octet-stream"} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 13: mime::setheader with different modes |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 13: mime::setheader modes ===" |
|
|
|
test "setheader write mode" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
mime::setheader $token X-Test "value1" -mode write |
|
set result [mime::getheader $token X-Test] |
|
mime::finalize $token |
|
expr {$result eq "value1"} |
|
} 1 |
|
|
|
test "setheader append mode" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
mime::setheader $token X-Test "value1" |
|
mime::setheader $token X-Test "value2" -mode append |
|
set result [mime::getheader $token X-Test] |
|
mime::finalize $token |
|
string match "*value1*value2*" $result |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 14: mime::encodingasciiP with various edge cases |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 14: mime::encodingasciiP edge cases ===" |
|
|
|
test "encodingasciiP with carriage return at line end" { |
|
# encodingasciiP checks for \r only at line ends, not in middle |
|
mime::encodingasciiP "Hello\nWorld" |
|
} 1 |
|
|
|
test "encodingasciiP with control characters" { |
|
mime::encodingasciiP "Hello\x00World" |
|
} 0 |
|
|
|
test "encodingasciiP with high ASCII" { |
|
mime::encodingasciiP "Hello\x80World" |
|
} 0 |
|
|
|
test "encodingasciiP with spaces and tabs" { |
|
mime::encodingasciiP "Hello \t World" |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 15: mime::qp_decode with various encodings |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 15: mime::qp_decode edge cases ===" |
|
|
|
test "qp_decode with multiple encoded chars" { |
|
set encoded "=48=65=6C=6C=6F" |
|
set result [mime::qp_decode $encoded] |
|
string match "*Hello*" $result |
|
} 1 |
|
|
|
test "qp_decode with trailing soft newline" { |
|
set encoded "Hello World=\n" |
|
set result [mime::qp_decode $encoded] |
|
# Soft newline removes the newline |
|
string match "*Hello World*" $result |
|
} 1 |
|
|
|
test "qp_decode with backslash" { |
|
set encoded "Hello\\\\World" |
|
set result [mime::qp_decode $encoded] |
|
# Backslash is protected for subst |
|
expr {[string length $result] > 0} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 16: mime::parseaddress with complex formats |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 16: mime::parseaddress complex formats ===" |
|
|
|
test "parseaddress with quoted string" { |
|
set result [mime::parseaddress "\"John Doe\" <john@example.com>"] |
|
llength $result |
|
} 1 |
|
|
|
test "parseaddress with route" { |
|
set result [mime::parseaddress "@route.com:user@example.com"] |
|
llength $result |
|
} 1 |
|
|
|
test "parseaddress with group" { |
|
set result [mime::parseaddress "Friends: user1@example.com, user2@example.com;"] |
|
expr {[llength $result] >= 2} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 17: mime::initialize with file content |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 17: mime::initialize with file ===" |
|
|
|
test "initialize with file content" { |
|
set tmpfile [file tempfile] |
|
set fd [open $tmpfile w] |
|
puts -nonewline $fd "Test file content" |
|
close $fd |
|
|
|
set token [mime::initialize -canonical "text/plain" -file $tmpfile] |
|
set result [mime::getproperty $token content] |
|
mime::finalize $token |
|
file delete $tmpfile |
|
|
|
expr {$result eq "text/plain"} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 18: mime::setheader with special headers |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 18: mime::setheader special headers ===" |
|
|
|
test "setheader Content-Type (cannot be set directly)" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
set result [catch {mime::setheader $token Content-Type "text/html; charset=utf-8" -internal 1} err] |
|
mime::finalize $token |
|
expr {$result == 1} |
|
} 1 |
|
|
|
test "setheader Content-Transfer-Encoding (cannot be set directly)" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
set result [catch {mime::setheader $token Content-Transfer-Encoding "base64" -internal 1} err] |
|
mime::finalize $token |
|
expr {$result == 1} |
|
} 1 |
|
|
|
test "setheader MIME-Version" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
mime::setheader $token MIME-Version "1.0" -internal 1 |
|
set result [mime::getheader $token MIME-Version] |
|
mime::finalize $token |
|
string match "*1.0*" $result |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 19: mime::getproperty with all properties |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 19: mime::getproperty all properties ===" |
|
|
|
test "getproperty params" { |
|
set token [mime::initialize -canonical "text/plain; charset=utf-8" -string "Test"] |
|
set result [mime::getproperty $token params] |
|
mime::finalize $token |
|
# params returns a list of key-value pairs |
|
expr {[llength $result] >= 0} |
|
} 1 |
|
|
|
test "getproperty all" { |
|
set token [mime::initialize -canonical "text/plain" -string "Test"] |
|
set result [mime::getproperty $token] |
|
mime::finalize $token |
|
expr {[llength $result] > 0} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Test 20: mime::copymessage with different encodings |
|
# ============================================================================ |
|
|
|
puts "\n=== Test Group 20: mime::copymessage with encodings ===" |
|
|
|
test "copymessage with different content types" { |
|
set token [mime::initialize -canonical "application/octet-stream" -string "Test content"] |
|
set tmpfile [file tempfile] |
|
set fd [open $tmpfile w+] |
|
mime::copymessage $token $fd |
|
close $fd |
|
file delete $tmpfile |
|
mime::finalize $token |
|
expr {1} |
|
} 1 |
|
|
|
test "copymessage with multipart" { |
|
set token [mime::initialize -canonical "multipart/mixed" -parts [list]] |
|
set tmpfile [file tempfile] |
|
set fd [open $tmpfile w+] |
|
mime::copymessage $token $fd |
|
close $fd |
|
file delete $tmpfile |
|
mime::finalize $token |
|
expr {1} |
|
} 1 |
|
|
|
# ============================================================================ |
|
# Summary |
|
# ============================================================================ |
|
|
|
puts "\n==========================================" |
|
puts "Test Summary" |
|
puts "==========================================" |
|
puts "Total Tests: $test_count" |
|
puts "Passed: $test_passed" |
|
puts "Failed: $test_failed" |
|
puts "==========================================" |
|
|
|
if {$test_failed > 0} { |
|
exit 1 |
|
} else { |
|
exit 0 |
|
}
|
|
|