yx_only_office_flutter/test/onlyoffice_viewer_unit_test...

358 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('OnlyOfficeViewer - 辅助函数单元测试', () {
test('文档类型识别 - Word 文档', () {
expect(TestHelper.getDocumentType('doc'), 'word');
expect(TestHelper.getDocumentType('docx'), 'word');
expect(TestHelper.getDocumentType('pdf'), 'word');
expect(TestHelper.getDocumentType('txt'), 'word');
expect(TestHelper.getDocumentType('rtf'), 'word');
expect(TestHelper.getDocumentType('html'), 'word');
expect(TestHelper.getDocumentType('epub'), 'word');
});
test('文档类型识别 - Excel 文档', () {
expect(TestHelper.getDocumentType('xls'), 'cell');
expect(TestHelper.getDocumentType('xlsx'), 'cell');
expect(TestHelper.getDocumentType('xlsm'), 'cell');
expect(TestHelper.getDocumentType('csv'), 'cell');
expect(TestHelper.getDocumentType('ods'), 'cell');
});
test('文档类型识别 - PowerPoint 文档', () {
expect(TestHelper.getDocumentType('ppt'), 'slide');
expect(TestHelper.getDocumentType('pptx'), 'slide');
expect(TestHelper.getDocumentType('pptm'), 'slide');
expect(TestHelper.getDocumentType('odp'), 'slide');
});
test('文档类型识别 - 未知扩展名默认为 word', () {
expect(TestHelper.getDocumentType('unknown'), 'word');
expect(TestHelper.getDocumentType('xyz'), 'word');
expect(TestHelper.getDocumentType(''), 'word');
});
test('文档密钥生成 - SHA256 哈希', () {
final url1 = 'https://example.com/test.docx';
final url2 = 'https://example.com/test.docx';
final url3 = 'https://example.com/different.docx';
// 相同 URL 应该生成相同的密钥
expect(TestHelper.generateDocKey(url1), TestHelper.generateDocKey(url2));
// 不同 URL 应该生成不同的密钥
expect(TestHelper.generateDocKey(url1), isNot(TestHelper.generateDocKey(url3)));
// 验证密钥是 SHA256 哈希64个字符的十六进制
final key = TestHelper.generateDocKey(url1);
expect(key.length, 64);
expect(RegExp(r'^[a-f0-9]{64}$').hasMatch(key), true);
});
test('文档密钥生成 - 真实文件 URL', () {
const realFileUrl = 'https://quanxue-oa.oss-cn-chengdu.aliyuncs.com/20250815/1755244744547.pptx';
final key = TestHelper.generateDocKey(realFileUrl);
expect(key, isNotEmpty);
expect(key.length, 64);
expect(RegExp(r'^[a-f0-9]{64}$').hasMatch(key), true);
});
test('URL 标准化 - 移除尾部斜杠', () {
expect(TestHelper.normalizeUrl('https://document.23544.com/'), 'https://document.23544.com');
expect(TestHelper.normalizeUrl('https://document.23544.com'), 'https://document.23544.com');
expect(TestHelper.normalizeUrl('https://example.com///'), 'https://example.com//');
});
test('URL 标准化 - 去除首尾空格', () {
expect(TestHelper.normalizeUrl(' https://document.23544.com/ '), 'https://document.23544.com');
expect(TestHelper.normalizeUrl('\nhttps://document.23544.com/\t'), 'https://document.23544.com');
expect(TestHelper.normalizeUrl(' https://example.com '), 'https://example.com');
});
test('配置生成 - 基本配置', () {
final config = TestHelper.createConfig(fileUrl: 'https://example.com/test.docx', jwtSecret: null);
expect(config['document'], isNotNull);
expect(config['document']['fileType'], 'docx');
expect(config['document']['url'], 'https://example.com/test.docx');
expect(config['document']['title'], 'test.docx');
expect(config['document']['key'], isNotNull);
expect(config['documentType'], 'word');
expect(config['editorConfig'], isNotNull);
expect(config['editorConfig']['mode'], 'view');
expect(config['editorConfig']['lang'], 'zh-CN');
expect(config['type'], 'mobile');
expect(config['token'], isNull);
});
test('配置生成 - 带 JWT secret 生成 token', () {
final config = TestHelper.createConfig(
fileUrl: 'https://quanxue-oa.oss-cn-chengdu.aliyuncs.com/20250815/1755244744547.pptx',
jwtSecret: '6Yr6DGoVV3ACS6GtVgdH453mXxLftd6Q',
);
// 验证 token 已生成
expect(config['token'], isNotNull);
expect(config['token'], isNotEmpty);
// 验证 token 是有效的 JWT 格式 (header.payload.signature)
final tokenParts = (config['token'] as String).split('.');
expect(tokenParts.length, 3);
expect(config['document']['fileType'], 'pptx');
expect(config['documentType'], 'slide');
});
test('配置生成 - 空 jwtSecret 不添加 token 到配置', () {
final config1 = TestHelper.createConfig(fileUrl: 'https://example.com/test.docx', jwtSecret: '');
expect(config1['token'], isNull);
final config2 = TestHelper.createConfig(fileUrl: 'https://example.com/test.docx', jwtSecret: null);
expect(config2['token'], isNull);
});
test('HTML 生成 - 包含必要元素', () {
final html = TestHelper.buildHtml(
serverUrl: 'https://document.23544.com/',
fileUrl: 'https://example.com/test.docx',
jwtSecret: null,
);
// 验证 HTML 结构
expect(html.contains('<!DOCTYPE html>'), true);
expect(html.contains('<html lang="en">'), true);
expect(html.contains('<meta charset="UTF-8">'), true);
expect(html.contains('<meta name="viewport"'), true);
expect(html.contains('<div id="placeholder"></div>'), true);
// 验证 API 脚本引用
expect(html.contains('https://document.23544.com/web-apps/apps/api/documents/api.js'), true);
// 验证 DocsAPI.DocEditor 调用
expect(html.contains('new DocsAPI.DocEditor("placeholder", config)'), true);
// 验证事件处理
expect(html.contains('"onAppReady"'), true);
expect(html.contains('"onDocumentReady"'), true);
expect(html.contains('"onError"'), true);
});
test('HTML 生成 - 真实数据配置', () {
final html = TestHelper.buildHtml(
serverUrl: 'https://document.23544.com',
fileUrl: 'https://quanxue-oa.oss-cn-chengdu.aliyuncs.com/20250815/1755244744547.pptx',
jwtSecret: '6Yr6DGoVV3ACS6GtVgdH453mXxLftd6Q',
);
// 验证配置包含 token (JWT 签名后的)
expect(html.contains('"token"'), true);
// 验证文件信息
expect(html.contains('1755244744547.pptx'), true);
expect(html.contains('"fileType":"pptx"'), true);
expect(html.contains('"documentType":"slide"'), true);
});
test('HTML 生成 - CSS 样式', () {
final html = TestHelper.buildHtml(
serverUrl: 'https://document.23544.com',
fileUrl: 'https://example.com/test.docx',
jwtSecret: null,
);
expect(html.contains('margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden;'), true);
expect(html.contains('#placeholder { height: 100%; }'), true);
});
test('真实服务器 URL 验证', () {
const realServerUrl = 'https://document.23544.com/';
final normalizedUrl = TestHelper.normalizeUrl(realServerUrl);
final expectedApiUrl = '$normalizedUrl/web-apps/apps/api/documents/api.js';
expect(normalizedUrl, 'https://document.23544.com');
expect(expectedApiUrl, 'https://document.23544.com/web-apps/apps/api/documents/api.js');
});
test('真实文件 URL 解析', () {
const realFileUrl = 'https://quanxue-oa.oss-cn-chengdu.aliyuncs.com/20250815/1755244744547.pptx';
final fileName = realFileUrl.split('/').last;
final fileExtension = fileName.split('.').last.toLowerCase();
expect(fileName, '1755244744547.pptx');
expect(fileExtension, 'pptx');
expect(TestHelper.getDocumentType(fileExtension), 'slide');
});
test('真实文件类型识别 - PPTX', () {
const realFileUrl = 'https://quanxue-oa.oss-cn-chengdu.aliyuncs.com/20250815/1755244744547.pptx';
final fileExtension = realFileUrl.split('.').last.toLowerCase();
final documentType = TestHelper.getDocumentType(fileExtension);
expect(documentType, 'slide');
});
test('JWT Secret 验证', () {
const realSecret = '6Yr6DGoVV3ACS6GtVgdH453mXxLftd6Q';
expect(realSecret, isNotNull);
expect(realSecret, isNotEmpty);
expect(realSecret.length, 32);
});
test('JWT Token 生成和验证', () {
const jwtSecret = '6Yr6DGoVV3ACS6GtVgdH453mXxLftd6Q';
const fileUrl = 'https://example.com/test.docx';
final config = TestHelper.createConfig(fileUrl: fileUrl, jwtSecret: jwtSecret);
expect(config['token'], isNotNull);
final token = config['token'] as String;
// 验证 token 格式
final tokenParts = token.split('.');
expect(tokenParts.length, 3); // header.payload.signature
// 验证 token 可以被解码和验证
final jwt = JWT.verify(token, SecretKey(jwtSecret));
expect(jwt.payload, isNotNull);
// 验证 payload 包含配置信息
final payload = jwt.payload as Map<String, dynamic>;
expect(payload['document'], isNotNull);
expect(payload['document']['key'], config['document']['key']);
expect(payload['documentType'], 'word');
});
test('文件 URL 格式验证', () {
const realFileUrl = 'https://quanxue-oa.oss-cn-chengdu.aliyuncs.com/20250815/1755244744547.pptx';
final uri = Uri.parse(realFileUrl);
expect(uri.scheme, 'https');
expect(uri.host, 'quanxue-oa.oss-cn-chengdu.aliyuncs.com');
expect(uri.path, '/20250815/1755244744547.pptx');
expect(uri.hasScheme, true);
expect(uri.hasAuthority, true);
});
});
}
/// 测试辅助类 - 复制 OnlyOfficeViewer 的内部逻辑用于测试
class TestHelper {
static String getDocumentType(String extension) {
const wordExtensions = [
'doc',
'docx',
'docm',
'dot',
'dotx',
'dotm',
'odt',
'fodt',
'ott',
'rtf',
'txt',
'html',
'htm',
'mht',
'pdf',
'djvu',
'fb2',
'epub',
'xps',
];
const cellExtensions = ['xls', 'xlsx', 'xlsm', 'xlt', 'xltx', 'xltm', 'ods', 'fods', 'ots', 'csv'];
const slideExtensions = ['ppt', 'pptx', 'pptm', 'pps', 'ppsx', 'ppsm', 'pot', 'potx', 'potm', 'odp', 'fodp', 'otp'];
if (wordExtensions.contains(extension)) return 'word';
if (cellExtensions.contains(extension)) return 'cell';
if (slideExtensions.contains(extension)) return 'slide';
return 'word';
}
static String generateDocKey(String url) {
return sha256.convert(utf8.encode(url)).toString();
}
static String normalizeUrl(String url) {
var trimmedUrl = url.trim();
if (trimmedUrl.endsWith('/')) {
return trimmedUrl.substring(0, trimmedUrl.length - 1);
}
return trimmedUrl;
}
static Map<String, dynamic> createConfig({required String fileUrl, required String? jwtSecret}) {
final fileExt = fileUrl.split('.').last.toLowerCase();
final documentType = getDocumentType(fileExt);
final config = {
'document': {
'fileType': fileExt,
'key': generateDocKey(fileUrl),
'title': fileUrl.split('/').last,
'url': fileUrl,
},
'documentType': documentType,
'editorConfig': {'mode': 'view', 'lang': 'zh-CN'},
'type': 'mobile',
};
// Sign the entire config with JWT if secret is provided
if (jwtSecret != null && jwtSecret.isNotEmpty) {
final jwt = JWT(config);
final token = jwt.sign(SecretKey(jwtSecret), algorithm: JWTAlgorithm.HS256);
config['token'] = token;
}
return config;
}
static String buildHtml({required String serverUrl, required String fileUrl, required String? jwtSecret}) {
final apiJsUrl = '${normalizeUrl(serverUrl)}/web-apps/apps/api/documents/api.js';
final config = createConfig(fileUrl: fileUrl, jwtSecret: jwtSecret);
final configJson = jsonEncode(config);
return '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>OnlyOffice Viewer</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden; }
#placeholder { height: 100%; }
</style>
</head>
<body>
<div id="placeholder"></div>
<script type="text/javascript" src="$apiJsUrl"></script>
<script type="text/javascript">
var config = $configJson;
config.events = {
"onAppReady": function() {
console.log('OnlyOffice App Ready');
},
"onDocumentReady": function() {
console.log('OnlyOffice Document Ready');
},
"onError": function(event) {
console.error('OnlyOffice Error:', event.data);
}
};
new DocsAPI.DocEditor("placeholder", config);
</script>
</body>
</html>
''';
}
}