diff --git a/Tests/SwiftIMAPTests/QuotedPrintableTests.swift b/Tests/SwiftIMAPTests/QuotedPrintableTests.swift
index b7f343e..abf1af4 100644
--- a/Tests/SwiftIMAPTests/QuotedPrintableTests.swift
+++ b/Tests/SwiftIMAPTests/QuotedPrintableTests.swift
@@ -2,6 +2,18 @@ import Foundation
import Testing
@testable import SwiftMail
+// Use existing tag definitions and add new ones
+extension Tag {
+ @Tag static var encoding: Self
+ @Tag static var decoding: Self
+ @Tag static var imap: Self
+ @Tag static var performance: Self
+ @Tag static var mime: Self
+ @Tag static var fileHandling: Self
+ @Tag static var security: Self
+}
+
+@Suite("Quoted-Printable Encoding Tests", .tags(.imap, .encoding, .decoding))
struct QuotedPrintableTests {
// MARK: - Test Resources
@@ -24,8 +36,8 @@ struct QuotedPrintableTests {
// MARK: - Basic Decoding Tests
- @Test
- func testBasicQuotedPrintableDecoding() {
+ @Test("Basic quoted-printable decoding", .tags(.decoding))
+ func basicQuotedPrintableDecoding() {
// Test basic quoted-printable decoding
let encoded = "Hello=20World"
#expect(encoded.decodeQuotedPrintable() == "Hello World")
@@ -43,8 +55,8 @@ struct QuotedPrintableTests {
#expect(equalsSign.decodeQuotedPrintable() == "3=2+1")
}
- @Test
- func testEncodingDetection() {
+ @Test("Encoding detection", .tags(.encoding))
+ func encodingDetection() {
// Test ISO-8859-1 encoding detection
let isoContent = "Content-Type: text/plain; charset=iso-8859-1\r\n\r\nThis has special chars: =E4=F6=FC=DF"
#expect(isoContent.detectCharsetEncoding() == .isoLatin1)
@@ -62,9 +74,8 @@ struct QuotedPrintableTests {
#expect(noCharset.detectCharsetEncoding() == .utf8)
}
- // Skip this test for now as the implementation might be different
- @Test
- func testAutoDetectionDecoding() {
+ @Test("Auto-detection decoding", .tags(.decoding))
+ func autoDetectionDecoding() {
// Simple test with basic content
let basicContent = "Hello=20World"
#expect(basicContent.decodeQuotedPrintable() == "Hello World")
@@ -72,8 +83,8 @@ struct QuotedPrintableTests {
// MARK: - MIME Header Tests
- @Test
- func testMIMEHeaderDecoding() {
+ @Test("MIME header decoding", .tags(.decoding, .mime))
+ func mimeHeaderDecoding() {
// Test Q-encoded header
let qEncoded = "=?UTF-8?Q?Hello=20World?="
#expect(qEncoded.decodeMIMEHeader() == "Hello World")
@@ -94,8 +105,8 @@ struct QuotedPrintableTests {
// MARK: - HTML File Tests
- @Test
- func testISO8859HTMLFile() throws {
+ @Test("ISO-8859-1 HTML file processing", .tags(.fileHandling))
+ func iso8859HTMLFile() throws {
let content = try loadResourceContent(name: "sample_quoted_printable", withExtension: "html")
// Test that the content was loaded
@@ -109,8 +120,8 @@ struct QuotedPrintableTests {
#expect(simpleTest.decodeQuotedPrintable() == "Hello World")
}
- @Test
- func testUTF8HTMLFile() throws {
+ @Test("UTF-8 HTML file processing", .tags(.fileHandling))
+ func utf8HTMLFile() throws {
let content = try loadResourceContent(name: "sample_quoted_printable_utf8", withExtension: "html")
// Test that the content was loaded
@@ -124,8 +135,8 @@ struct QuotedPrintableTests {
#expect(simpleTest.decodeQuotedPrintable() == "Hello World")
}
- @Test
- func testMIMEHeaderFile() throws {
+ @Test("MIME header file processing", .tags(.fileHandling, .mime))
+ func mimeHeaderFile() throws {
let content = try loadResourceContent(name: "sample_mime_header", withExtension: "txt")
// Split the content into lines
@@ -155,6 +166,39 @@ struct QuotedPrintableTests {
throw TestFailure("Subject header not found")
}
}
+
+ // MARK: - Additional Edge Cases
+
+ @Test("Edge cases and malformed input", .tags(.decoding, .security))
+ func edgeCasesAndMalformedInput() {
+ // Test empty string
+ #expect("".decodeQuotedPrintable() == "")
+
+ // Test string without encoding
+ #expect("Plain text".decodeQuotedPrintable() == "Plain text")
+
+ // Test malformed encoding (incomplete hex) - should return nil for invalid input
+ let malformed = "Hello=2World" // Missing second hex digit
+ let result = malformed.decodeQuotedPrintable()
+ #expect(result == nil, "Should return nil for malformed input")
+
+ // Test with invalid hex characters - should return nil for invalid input
+ let invalidHex = "Hello=ZZ"
+ let invalidResult = invalidHex.decodeQuotedPrintable()
+ #expect(invalidResult == nil, "Should return nil for invalid hex")
+ }
+
+ @Test("Performance with large content", .tags(.performance, .decoding))
+ func performanceWithLargeContent() {
+ // Create a large quoted-printable encoded string
+ let baseString = "This=20is=20a=20test=20string=20with=20spaces=0D=0A"
+ let largeContent = String(repeating: baseString, count: 1000)
+
+ // Test that decoding completes without hanging
+ let decoded = largeContent.decodeQuotedPrintable()
+ #expect(decoded != nil, "Should decode large content successfully")
+ #expect(decoded?.contains("This is a test string with spaces") ?? false, "Should contain expected decoded content")
+ }
}
// Custom error type for test failures
diff --git a/Tests/SwiftMailCoreTests/String+EmailTests.swift b/Tests/SwiftMailCoreTests/String+EmailTests.swift
index aacb6e7..4c0e28b 100644
--- a/Tests/SwiftMailCoreTests/String+EmailTests.swift
+++ b/Tests/SwiftMailCoreTests/String+EmailTests.swift
@@ -1,51 +1,152 @@
// String+EmailTests.swift
// Tests for email validation String extension
-import XCTest
+import Testing
@testable import SwiftMail
-final class StringEmailTests: XCTestCase {
- func testValidEmails() {
- // Test basic valid formats
- XCTAssertTrue("user@example.com".isValidEmail())
- XCTAssertTrue("user.name@example.com".isValidEmail())
- XCTAssertTrue("user+tag@example.com".isValidEmail())
- XCTAssertTrue("user@subdomain.example.com".isValidEmail())
- XCTAssertTrue("123@example.com".isValidEmail())
- XCTAssertTrue("user@example.co.uk".isValidEmail())
-
- // Test edge cases that should be valid
- XCTAssertTrue("a@b.cc".isValidEmail()) // Minimal length
- XCTAssertTrue("disposable.style.email.with+symbol@example.com".isValidEmail())
- XCTAssertTrue("other.email-with-hyphen@example.com".isValidEmail())
- XCTAssertTrue("fully-qualified-domain@example.com".isValidEmail())
- XCTAssertTrue("user.name+tag+sorting@example.com".isValidEmail())
- XCTAssertTrue("x@example.com".isValidEmail()) // One-letter local-part
- XCTAssertTrue("example-indeed@strange-example.com".isValidEmail())
- XCTAssertTrue("example@s.example".isValidEmail()) // Short but valid domain
+// Use existing tag definitions
+extension Tag {
+ @Tag static var validation: Self
+ @Tag static var security: Self
+}
+
+@Suite("String Email Validation Tests", .tags(.core, .validation))
+struct StringEmailTests {
+
+ @Test("Valid email addresses should pass validation", .tags(.validation),
+ arguments: [
+ "user@example.com",
+ "user.name@example.com",
+ "user+tag@example.com",
+ "user@subdomain.example.com",
+ "123@example.com",
+ "user@example.co.uk",
+ "a@b.cc", // Minimal length
+ "disposable.style.email.with+symbol@example.com",
+ "other.email-with-hyphen@example.com",
+ "fully-qualified-domain@example.com",
+ "user.name+tag+sorting@example.com",
+ "x@example.com", // One-letter local-part
+ "example-indeed@strange-example.com",
+ "example@s.example", // Short but valid domain
+ "test.email.with+symbol@example.com",
+ "user123@test-domain.org"
+ ])
+ func validEmails(email: String) {
+ #expect(email.isValidEmail(), "'\(email)' should be a valid email address")
}
- func testInvalidEmails() {
- // Test basic invalid formats
- XCTAssertFalse("".isValidEmail())
- XCTAssertFalse("@example.com".isValidEmail())
- XCTAssertFalse("user@".isValidEmail())
- XCTAssertFalse("user@.com".isValidEmail())
- XCTAssertFalse("user@example".isValidEmail())
- XCTAssertFalse("user.example.com".isValidEmail())
+ @Test("Invalid email addresses should fail validation", .tags(.validation, .security),
+ arguments: [
+ "",
+ "@example.com",
+ "user@",
+ "user@.com",
+ "user@example",
+ "user.example.com",
+ "user@exam ple.com", // Space in domain
+ "user@@example.com", // Double @
+ ".user@example.com", // Leading dot
+ "user.@example.com", // Trailing dot
+ "user@example..com", // Double dot
+ "user@-example.com", // Leading hyphen in domain
+ "user@example-.com", // Trailing hyphen in domain
+ "user@.example.com", // Leading dot in domain
+ "user@example.", // Trailing dot in domain
+ "user@ex*ample.com", // Invalid character
+ "user@example.c", // TLD too short
+ "user name@example.com", // Space in local part
+ "user@example.com",
+ "user'; DROP TABLE users; --@example.com",
+ "user@example.com\\r\\nBCC: evil@hacker.com",
+ "user@example.com\\nSubject: Spam",
+ "user\\u{0000}@example.com", // Null byte injection
+ "user@exam\\u{0000}ple.com"
+ ]
+
+ for maliciousEmail in maliciousInputs {
+ #expect(!maliciousEmail.isValidEmail(),
+ "Malicious input '\(maliciousEmail)' should be rejected")
+ }
+ }
+
+ @Test("Performance with long inputs", .tags(.validation))
+ func performanceLongInputs() {
+ // Test very long email addresses
+ let longLocalPart = String(repeating: "a", count: 1000)
+ let longEmail = "\(longLocalPart)@example.com"
+
+ // Should handle long inputs gracefully (likely invalid due to length)
+ let result = longEmail.isValidEmail()
+ #expect(!result, "Extremely long email should be invalid")
+
+ // Test long domain
+ let longDomain = String(repeating: "test.", count: 50) + "com"
+ let longDomainEmail = "user@\(longDomain)"
+ let domainResult = longDomainEmail.isValidEmail()
+ // Just verify it handles the input without crashing
+ #expect(domainResult is Bool, "Email validation should handle long domains gracefully")
+ }
+
+ @Test("Unicode and special character handling", .tags(.validation))
+ func unicodeHandling() {
+ let unicodeEmails = [
+ "用户@example.com", // Chinese characters in local part
+ "user@раздел.com", // Cyrillic in domain
+ "tëst@example.com", // Accented characters
+ "user+tag@example.com", // Plus sign (should be valid)
+ "user=tag@example.com", // Equals sign
+ "user%tag@example.com" // Percent sign
+ ]
+
+ for email in unicodeEmails {
+ let result = email.isValidEmail()
+ // Implementation may vary on unicode support
+ // Just verify that the function doesn't crash and returns a boolean
+ #expect(result is Bool, "Email validation should return a boolean for '\(email)'")
+ }
+
+ // Test that plus sign is typically allowed (common use case)
+ #expect("user+tag@example.com".isValidEmail(), "Plus sign should typically be allowed in email addresses")
}
}
\ No newline at end of file
diff --git a/Tests/SwiftMailCoreTests/String+HostnameTests.swift b/Tests/SwiftMailCoreTests/String+HostnameTests.swift
index 245db0d..73fb2a0 100644
--- a/Tests/SwiftMailCoreTests/String+HostnameTests.swift
+++ b/Tests/SwiftMailCoreTests/String+HostnameTests.swift
@@ -1,38 +1,43 @@
// String+HostnameTests.swift
// Tests for hostname-related String extensions
-import XCTest
+import Testing
+import Foundation
@testable import SwiftMail
-final class StringHostnameTests: XCTestCase {
- func testLocalHostname() {
+@Suite("String Hostname Extensions Tests")
+struct StringHostnameTests {
+
+ @Test("Local hostname resolution returns valid hostname")
+ func localHostname() {
let hostname = String.localHostname
// Test that hostname is not empty
- XCTAssertFalse(hostname.isEmpty)
+ #expect(!hostname.isEmpty, "Hostname should not be empty")
// Test that hostname is not the fallback value unless all other methods fail
if !hostname.hasPrefix("[") && !hostname.hasSuffix("]") {
// If it's not an IP address format, it should be a valid hostname
- XCTAssertNotEqual(hostname, "localhost")
- XCTAssertNotEqual(hostname, "swift-mail-client.local")
+ #expect(hostname != "localhost", "Should not default to localhost unless necessary")
+ #expect(hostname != "swift-mail-client.local", "Should not default to fallback unless necessary")
}
// Test hostname format
if hostname.hasPrefix("[") && hostname.hasSuffix("]") {
// IP address format
let ip = String(hostname.dropFirst().dropLast())
- XCTAssertTrue(isValidIP(ip), "Invalid IP address format: \(ip)")
+ #expect(isValidIP(ip), "Invalid IP address format: \(ip)")
} else {
// Hostname format
- XCTAssertTrue(isValidHostname(hostname), "Invalid hostname format: \(hostname)")
+ #expect(isValidHostname(hostname), "Invalid hostname format: \(hostname)")
}
}
- func testLocalIPAddress() {
+ @Test("Local IP address resolution returns valid IP when available")
+ func localIPAddress() {
if let ipAddress = String.localIPAddress {
// Test that we got a valid IP address
- XCTAssertTrue(isValidIP(ipAddress), "Invalid IP address format: \(ipAddress)")
+ #expect(isValidIP(ipAddress), "Invalid IP address format: \(ipAddress)")
}
// Note: We don't fail if no IP is found, as this might be legitimate in some environments
}
diff --git a/Tests/SwiftMailCoreTests/String+MIMETests.swift b/Tests/SwiftMailCoreTests/String+MIMETests.swift
index 0397ba3..a52dd55 100644
--- a/Tests/SwiftMailCoreTests/String+MIMETests.swift
+++ b/Tests/SwiftMailCoreTests/String+MIMETests.swift
@@ -1,57 +1,118 @@
// String+MIMETests.swift
// Tests for MIME-related String extensions
-import XCTest
+import Testing
@testable import SwiftMail
-final class StringMIMETests: XCTestCase {
- func testFileExtensionForMIMEType() {
+// MARK: - Tag Definitions
+extension Tag {
+ @Tag static var core: Self
+ @Tag static var mime: Self
+ @Tag static var fileHandling: Self
+ @Tag static var crossPlatform: Self
+}
+
+@Suite("String MIME Extensions Tests", .tags(.core, .mime, .fileHandling))
+struct StringMIMETests {
+
+ @Test("File extension for MIME type resolution", .tags(.crossPlatform))
+ func fileExtensionForMIMEType() {
#if os(macOS)
// On macOS, we use UTType which might return different extensions
// We only test that we get a valid extension back
if let jpegExt = String.fileExtension(for: "image/jpeg") {
- XCTAssertTrue(["jpg", "jpeg"].contains(jpegExt))
+ #expect(["jpg", "jpeg"].contains(jpegExt))
} else {
- XCTFail("Failed to get extension for image/jpeg")
+ Issue.record("Failed to get extension for image/jpeg")
}
#else
// Test common MIME types
- XCTAssertEqual(String.fileExtension(for: "image/jpeg"), "jpg")
- XCTAssertEqual(String.fileExtension(for: "image/png"), "png")
- XCTAssertEqual(String.fileExtension(for: "application/pdf"), "pdf")
- XCTAssertEqual(String.fileExtension(for: "text/plain"), "txt")
- XCTAssertEqual(String.fileExtension(for: "text/html"), "html")
+ #expect(String.fileExtension(for: "image/jpeg") == "jpg")
+ #expect(String.fileExtension(for: "image/png") == "png")
+ #expect(String.fileExtension(for: "application/pdf") == "pdf")
+ #expect(String.fileExtension(for: "text/plain") == "txt")
+ #expect(String.fileExtension(for: "text/html") == "html")
// Test Office document types
- XCTAssertEqual(String.fileExtension(for: "application/msword"), "doc")
- XCTAssertEqual(String.fileExtension(for: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), "docx")
- XCTAssertEqual(String.fileExtension(for: "application/vnd.ms-excel"), "xls")
+ #expect(String.fileExtension(for: "application/msword") == "doc")
+ #expect(String.fileExtension(for: "application/vnd.openxmlformats-officedocument.wordprocessingml.document") == "docx")
+ #expect(String.fileExtension(for: "application/vnd.ms-excel") == "xls")
#endif
// Test unknown MIME type (should work the same on all platforms)
- XCTAssertNil(String.fileExtension(for: "application/unknown"))
+ #expect(String.fileExtension(for: "application/unknown") == nil)
}
- func testMIMETypeForFileExtension() {
+ @Test("MIME type for file extension resolution", .tags(.crossPlatform))
+ func mimeTypeForFileExtension() {
// Test common file extensions (should work the same on all platforms)
- XCTAssertEqual(String.mimeType(for: "jpg"), "image/jpeg")
- XCTAssertEqual(String.mimeType(for: "jpeg"), "image/jpeg")
- XCTAssertEqual(String.mimeType(for: "png"), "image/png")
- XCTAssertEqual(String.mimeType(for: "pdf"), "application/pdf")
- XCTAssertEqual(String.mimeType(for: "txt"), "text/plain")
- XCTAssertEqual(String.mimeType(for: "html"), "text/html")
- XCTAssertEqual(String.mimeType(for: "htm"), "text/html")
+ #expect(String.mimeType(for: "jpg") == "image/jpeg")
+ #expect(String.mimeType(for: "jpeg") == "image/jpeg")
+ #expect(String.mimeType(for: "png") == "image/png")
+ #expect(String.mimeType(for: "pdf") == "application/pdf")
+ #expect(String.mimeType(for: "txt") == "text/plain")
+ #expect(String.mimeType(for: "html") == "text/html")
+ #expect(String.mimeType(for: "htm") == "text/html")
// Test Office file extensions
- XCTAssertEqual(String.mimeType(for: "doc"), "application/msword")
- XCTAssertEqual(String.mimeType(for: "docx"), "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
- XCTAssertEqual(String.mimeType(for: "xls"), "application/vnd.ms-excel")
+ #expect(String.mimeType(for: "doc") == "application/msword")
+ #expect(String.mimeType(for: "docx") == "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
+ #expect(String.mimeType(for: "xls") == "application/vnd.ms-excel")
// Test case insensitivity
- XCTAssertEqual(String.mimeType(for: "JPG"), "image/jpeg")
- XCTAssertEqual(String.mimeType(for: "PDF"), "application/pdf")
+ #expect(String.mimeType(for: "JPG") == "image/jpeg")
+ #expect(String.mimeType(for: "PDF") == "application/pdf")
// Test unknown extension
- XCTAssertEqual(String.mimeType(for: "unknown"), "application/octet-stream")
+ #expect(String.mimeType(for: "unknown") == "application/octet-stream")
+ }
+
+ @Test("Extended MIME type support", .tags(.fileHandling))
+ func extendedMIMETypes() {
+ // Test additional multimedia types - verify they return appropriate types
+ let mp4Type = String.mimeType(for: "mp4")
+ #expect(mp4Type.hasPrefix("video/"), "MP4 should be a video type, got: \(mp4Type)")
+
+ let mp3Type = String.mimeType(for: "mp3")
+ #expect(mp3Type.hasPrefix("audio/"), "MP3 should be an audio type, got: \(mp3Type)")
+
+ let wavType = String.mimeType(for: "wav")
+ #expect(wavType.hasPrefix("audio/"), "WAV should be an audio type, got: \(wavType)")
+
+ let gifType = String.mimeType(for: "gif")
+ #expect(gifType.hasPrefix("image/"), "GIF should be an image type, got: \(gifType)")
+
+ let bmpType = String.mimeType(for: "bmp")
+ #expect(bmpType.hasPrefix("image/"), "BMP should be an image type, got: \(bmpType)")
+
+ // Test archive types
+ let zipType = String.mimeType(for: "zip")
+ #expect(zipType.hasPrefix("application/"), "ZIP should be an application type, got: \(zipType)")
+
+ // Test development file types
+ let jsonType = String.mimeType(for: "json")
+ #expect(jsonType.hasPrefix("application/") || jsonType.hasPrefix("text/"),
+ "JSON should be application or text type, got: \(jsonType)")
+
+ let cssType = String.mimeType(for: "css")
+ #expect(cssType.hasPrefix("text/"), "CSS should be a text type, got: \(cssType)")
+
+ let jsType = String.mimeType(for: "js")
+ #expect(jsType.hasPrefix("application/") || jsType.hasPrefix("text/"),
+ "JavaScript should be application or text type, got: \(jsType)")
+ }
+
+ @Test("Edge cases for MIME type resolution")
+ func edgeCases() {
+ // Test empty string
+ #expect(String.mimeType(for: "") == "application/octet-stream")
+
+ // Test very long extension
+ let longExtension = String(repeating: "a", count: 100)
+ #expect(String.mimeType(for: longExtension) == "application/octet-stream")
+
+ // Test extension with special characters
+ #expect(String.mimeType(for: "file.exe") == "application/octet-stream")
+ #expect(String.mimeType(for: "test@test") == "application/octet-stream")
}
}
\ No newline at end of file
diff --git a/Tests/SwiftMailCoreTests/String+UtilitiesTests.swift b/Tests/SwiftMailCoreTests/String+UtilitiesTests.swift
index 64bcf43..7cd463d 100644
--- a/Tests/SwiftMailCoreTests/String+UtilitiesTests.swift
+++ b/Tests/SwiftMailCoreTests/String+UtilitiesTests.swift
@@ -1,24 +1,27 @@
// String+UtilitiesTests.swift
// Tests for general String utilities
-import XCTest
+import Testing
@testable import SwiftMail
-final class StringUtilitiesTests: XCTestCase {
- func testSanitizedFileName() {
+@Suite("String Utilities Tests")
+struct StringUtilitiesTests {
+
+ @Test("Sanitized file name validation")
+ func sanitizedFileName() {
// Test valid filenames remain unchanged
- XCTAssertEqual("document.txt".sanitizedFileName(), "document.txt")
- XCTAssertEqual("image.jpg".sanitizedFileName(), "image.jpg")
+ #expect("document.txt".sanitizedFileName() == "document.txt")
+ #expect("image.jpg".sanitizedFileName() == "image.jpg")
// Test invalid characters are replaced
- XCTAssertEqual("file:with/invalid\\chars?.txt".sanitizedFileName(), "file_with_invalid_chars_.txt")
- XCTAssertEqual("doc*with|special.pdf".sanitizedFileName(), "doc_with_special_chars_.pdf")
+ #expect("file:with/invalid\\chars?.txt".sanitizedFileName() == "file_with_invalid_chars_.txt")
+ #expect("doc*with|special.pdf".sanitizedFileName() == "doc_with_special_chars_.pdf")
// Test spaces are replaced with underscores
- XCTAssertEqual("my document.pdf".sanitizedFileName(), "my_document.pdf")
- XCTAssertEqual("file with spaces.txt".sanitizedFileName(), "file_with_spaces.txt")
+ #expect("my document.pdf".sanitizedFileName() == "my_document.pdf")
+ #expect("file with spaces.txt".sanitizedFileName() == "file_with_spaces.txt")
// Test empty string
- XCTAssertEqual("".sanitizedFileName(), "")
+ #expect("".sanitizedFileName() == "")
}
}
\ No newline at end of file