MailHub v1.0.0: 跨平台邮箱客户端(纯 Dart IMAP/SMTP/MIME 协议栈)
- 登录: QQ/163/126/189/搜狐/Gmail/Outlook/Yahoo/iCloud/企业邮/自定义, 自动识别+测试连接 - 收信: IMAP 多文件夹同步/未读/星标/搜索(本地+服务器) - 读信: HTML渲染/CID内嵌图/原文查看/附件下载 - 写信: 收件人/抄送/密送/附件/富文本/草稿/回复转发 - 存储: SQLite + 系统安全存储(DPAPI/Keychain/Keystore) - 测试: 单元测试 + Mock IMAP/SMTP 服务器集成测试(23项全过) - 可迁移: Windows/Android/iOS/macOS/Linux 一套代码
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mail_hub/core/models/address.dart';
|
||||
import 'package:mail_hub/core/protocol/mime/mime_builder.dart';
|
||||
import 'package:mail_hub/core/protocol/smtp/smtp_client.dart';
|
||||
import 'package:mail_hub/core/protocol/smtp/smtp_exception.dart';
|
||||
|
||||
/// SMTP 客户端集成测试:连接本地 Mock 服务器,验证完整发信流程
|
||||
void main() {
|
||||
Process? server;
|
||||
int port = 0;
|
||||
final outFile = '${Directory.systemTemp.path}/mock_smtp_${DateTime.now().millisecondsSinceEpoch}.eml';
|
||||
|
||||
setUpAll(() async {
|
||||
final probe = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
|
||||
port = probe.port;
|
||||
await probe.close();
|
||||
|
||||
server = await Process.start('python3', [
|
||||
'test/mock_smtp_server.py',
|
||||
'$port',
|
||||
'--auth-user',
|
||||
'test@example.com',
|
||||
'--auth-pass',
|
||||
'secret',
|
||||
'--out',
|
||||
outFile,
|
||||
]);
|
||||
await server!.stdout.transform(utf8.decoder).first;
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
server?.kill();
|
||||
final f = File(outFile);
|
||||
if (await f.exists()) await f.delete();
|
||||
});
|
||||
|
||||
test('完整流程:连接→EHLO→AUTH→发送→QUIT', () async {
|
||||
final client = SmtpClient();
|
||||
await client.connect('127.0.0.1', port, ssl: false);
|
||||
expect(client.isConnected, true);
|
||||
expect(client.hasCapability('AUTH'), true);
|
||||
|
||||
await client.auth('test@example.com', 'secret');
|
||||
|
||||
final raw = MimeBuilder.build(
|
||||
from: const MailAddress('张三', 'zhangsan@example.com'),
|
||||
to: const [MailAddress('李四', 'lisi@example.com')],
|
||||
cc: const [MailAddress('王五', 'wangwu@example.com')],
|
||||
subject: 'SMTP 集成测试',
|
||||
textBody: '这是正文',
|
||||
htmlBody: '<b>这是正文</b>',
|
||||
);
|
||||
await client.sendMail('zhangsan@example.com', [
|
||||
'lisi@example.com',
|
||||
'wangwu@example.com',
|
||||
'secret-bcc@example.com',
|
||||
], raw);
|
||||
client.quit();
|
||||
client.disconnect();
|
||||
expect(client.isConnected, false);
|
||||
|
||||
// 校验 mock 收到的邮件内容
|
||||
final eml = await File(outFile).readAsString();
|
||||
expect(eml, contains('From: =?UTF-8?B?5byg5LiJ?= <zhangsan@example.com>'));
|
||||
expect(eml, contains('To: =?UTF-8?B?5p2O5Zub?= <lisi@example.com>'));
|
||||
expect(eml, contains('Cc: =?UTF-8?B?546L5LqU?= <wangwu@example.com>'));
|
||||
expect(eml, contains('Subject: =?UTF-8?B?U01UUCDpm4bmiJDmtYvor5U=?='));
|
||||
expect(eml, contains('multipart/alternative'));
|
||||
// 无附件时不包裹 mixed
|
||||
expect(eml, isNot(contains('multipart/mixed')));
|
||||
expect(eml, contains('MOCK_MSG_END'));
|
||||
});
|
||||
|
||||
test('认证失败抛出 SmtpException', () async {
|
||||
final client = SmtpClient();
|
||||
await client.connect('127.0.0.1', port, ssl: false);
|
||||
await expectLater(
|
||||
client.auth('test@example.com', 'wrong'),
|
||||
throwsA(isA<SmtpException>()),
|
||||
);
|
||||
client.disconnect();
|
||||
});
|
||||
|
||||
test('连接被拒抛出 SmtpException', () async {
|
||||
final client = SmtpClient();
|
||||
await expectLater(
|
||||
client.connect('127.0.0.1', 1, ssl: false),
|
||||
throwsA(isA<SmtpException>()),
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user