mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-08-31 22:09:06 +00:00
fix: 修复GraalPy依赖并添加Context池化和完整单元测试
- 修复parser pom.xml中GraalPy依赖配置 - 修复web-front Playground.vue中Tab选中异常bug - 添加PyContextPool实现Context池化管理 - 更新PyPlaygroundExecutor和PyParserExecutor使用池化 - 创建PyParserTest完整单元测试 - 创建PyHttpClientTest HTTP客户端测试 - 创建PyCryptoUtilsTest加密工具测试 - 修复所有ShareLinkInfo构造相关错误
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
package cn.qaiu.parser;
|
||||
|
||||
import cn.qaiu.parser.custompy.PyCryptoUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* PyCryptoUtils 测试类
|
||||
* 测试Python加密工具功能
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* Create at 2026/1/11
|
||||
*/
|
||||
public class PyCryptoUtilsTest {
|
||||
|
||||
private PyCryptoUtils cryptoUtils;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cryptoUtils = new PyCryptoUtils();
|
||||
System.out.println("--- 测试开始 ---");
|
||||
}
|
||||
|
||||
// ===================== MD5 测试 =====================
|
||||
|
||||
@Test
|
||||
public void testMd5() {
|
||||
System.out.println("\n[测试] MD5哈希");
|
||||
|
||||
// 测试已知值
|
||||
String input = "hello";
|
||||
String expected = "5d41402abc4b2a76b9719d911017c592";
|
||||
|
||||
String result = cryptoUtils.md5(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("MD5: " + result);
|
||||
System.out.println("期望: " + expected);
|
||||
|
||||
assertEquals("MD5结果应该正确", expected, result);
|
||||
assertEquals("MD5应该是32位", 32, result.length());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMd5_16() {
|
||||
System.out.println("\n[测试] MD5-16位哈希");
|
||||
|
||||
String input = "hello";
|
||||
String fullMd5 = "5d41402abc4b2a76b9719d911017c592";
|
||||
String expected = fullMd5.substring(8, 24); // "abc4b2a76b9719d9"
|
||||
|
||||
String result = cryptoUtils.md5_16(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("MD5-16: " + result);
|
||||
System.out.println("期望: " + expected);
|
||||
|
||||
assertEquals("MD5-16结果应该正确", expected, result);
|
||||
assertEquals("MD5-16应该是16位", 16, result.length());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMd5EmptyString() {
|
||||
System.out.println("\n[测试] MD5空字符串");
|
||||
|
||||
String input = "";
|
||||
String expected = "d41d8cd98f00b204e9800998ecf8427e";
|
||||
|
||||
String result = cryptoUtils.md5(input);
|
||||
|
||||
System.out.println("输入: (空字符串)");
|
||||
System.out.println("MD5: " + result);
|
||||
|
||||
assertEquals("空字符串MD5应该正确", expected, result);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
// ===================== SHA 测试 =====================
|
||||
|
||||
@Test
|
||||
public void testSha1() {
|
||||
System.out.println("\n[测试] SHA-1哈希");
|
||||
|
||||
String input = "hello";
|
||||
String expected = "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d";
|
||||
|
||||
String result = cryptoUtils.sha1(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("SHA-1: " + result);
|
||||
|
||||
assertEquals("SHA-1结果应该正确", expected, result);
|
||||
assertEquals("SHA-1应该是40位", 40, result.length());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSha256() {
|
||||
System.out.println("\n[测试] SHA-256哈希");
|
||||
|
||||
String input = "hello";
|
||||
String expected = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
|
||||
|
||||
String result = cryptoUtils.sha256(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("SHA-256: " + result);
|
||||
|
||||
assertEquals("SHA-256结果应该正确", expected, result);
|
||||
assertEquals("SHA-256应该是64位", 64, result.length());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSha512() {
|
||||
System.out.println("\n[测试] SHA-512哈希");
|
||||
|
||||
String input = "hello";
|
||||
|
||||
String result = cryptoUtils.sha512(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("SHA-512: " + result);
|
||||
|
||||
assertNotNull("SHA-512结果不能为null", result);
|
||||
assertEquals("SHA-512应该是128位", 128, result.length());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
// ===================== Base64 测试 =====================
|
||||
|
||||
@Test
|
||||
public void testBase64Encode() {
|
||||
System.out.println("\n[测试] Base64编码");
|
||||
|
||||
String input = "hello world";
|
||||
String expected = "aGVsbG8gd29ybGQ=";
|
||||
|
||||
String result = cryptoUtils.base64_encode(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("Base64: " + result);
|
||||
|
||||
assertEquals("Base64编码应该正确", expected, result);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64Decode() {
|
||||
System.out.println("\n[测试] Base64解码");
|
||||
|
||||
String input = "aGVsbG8gd29ybGQ=";
|
||||
String expected = "hello world";
|
||||
|
||||
String result = cryptoUtils.base64_decode(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("解码: " + result);
|
||||
|
||||
assertEquals("Base64解码应该正确", expected, result);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64EncodeBytes() {
|
||||
System.out.println("\n[测试] Base64字节编码");
|
||||
|
||||
byte[] input = "hello".getBytes(StandardCharsets.UTF_8);
|
||||
String expected = "aGVsbG8=";
|
||||
|
||||
String result = cryptoUtils.base64_encode_bytes(input);
|
||||
|
||||
System.out.println("输入字节数: " + input.length);
|
||||
System.out.println("Base64: " + result);
|
||||
|
||||
assertEquals("Base64字节编码应该正确", expected, result);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64UrlEncode() {
|
||||
System.out.println("\n[测试] Base64 URL安全编码");
|
||||
|
||||
// 包含特殊字符的测试数据
|
||||
String input = "hello+world/test";
|
||||
|
||||
String result = cryptoUtils.base64_url_encode(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("Base64 URL: " + result);
|
||||
|
||||
assertNotNull("结果不能为null", result);
|
||||
assertFalse("URL安全编码不应该包含+", result.contains("+"));
|
||||
assertFalse("URL安全编码不应该包含/", result.contains("/"));
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64UrlDecode() {
|
||||
System.out.println("\n[测试] Base64 URL安全解码");
|
||||
|
||||
String input = "aGVsbG8td29ybGQ";
|
||||
String expected = "hello-world";
|
||||
|
||||
String result = cryptoUtils.base64_url_decode(input);
|
||||
|
||||
System.out.println("输入: " + input);
|
||||
System.out.println("解码: " + result);
|
||||
|
||||
assertEquals("Base64 URL解码应该正确", expected, result);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64RoundTrip() {
|
||||
System.out.println("\n[测试] Base64编解码往返");
|
||||
|
||||
String[] testCases = {
|
||||
"hello",
|
||||
"hello world",
|
||||
"中文测试",
|
||||
"特殊字符!@#$%^&*()",
|
||||
""
|
||||
};
|
||||
|
||||
for (String original : testCases) {
|
||||
String encoded = cryptoUtils.base64_encode(original);
|
||||
String decoded = cryptoUtils.base64_decode(encoded);
|
||||
|
||||
assertEquals("编解码往返应该得到原值: " + original, original, decoded);
|
||||
}
|
||||
|
||||
System.out.println("✓ 测试通过(" + testCases.length + " 个测试用例)");
|
||||
}
|
||||
|
||||
// ===================== AES 测试 =====================
|
||||
|
||||
@Test
|
||||
public void testAesEcbEncryptDecrypt() {
|
||||
System.out.println("\n[测试] AES ECB模式加解密");
|
||||
|
||||
String plaintext = "hello world 123";
|
||||
String key = "1234567890123456"; // 16字节密钥
|
||||
|
||||
// 加密
|
||||
String encrypted = cryptoUtils.aes_encrypt_ecb(plaintext, key);
|
||||
System.out.println("原文: " + plaintext);
|
||||
System.out.println("密钥: " + key);
|
||||
System.out.println("密文: " + encrypted);
|
||||
|
||||
assertNotNull("加密结果不能为null", encrypted);
|
||||
assertNotEquals("加密后应该不同于原文", plaintext, encrypted);
|
||||
|
||||
// 解密
|
||||
String decrypted = cryptoUtils.aes_decrypt_ecb(encrypted, key);
|
||||
System.out.println("解密: " + decrypted);
|
||||
|
||||
assertEquals("解密后应该恢复原文", plaintext, decrypted);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAesCbcEncryptDecrypt() {
|
||||
System.out.println("\n[测试] AES CBC模式加解密");
|
||||
|
||||
String plaintext = "hello world 123";
|
||||
String key = "1234567890123456"; // 16字节密钥
|
||||
String iv = "abcdefghijklmnop"; // 16字节IV
|
||||
|
||||
// 加密
|
||||
String encrypted = cryptoUtils.aes_encrypt_cbc(plaintext, key, iv);
|
||||
System.out.println("原文: " + plaintext);
|
||||
System.out.println("密钥: " + key);
|
||||
System.out.println("IV: " + iv);
|
||||
System.out.println("密文: " + encrypted);
|
||||
|
||||
assertNotNull("加密结果不能为null", encrypted);
|
||||
assertNotEquals("加密后应该不同于原文", plaintext, encrypted);
|
||||
|
||||
// 解密
|
||||
String decrypted = cryptoUtils.aes_decrypt_cbc(encrypted, key, iv);
|
||||
System.out.println("解密: " + decrypted);
|
||||
|
||||
assertEquals("解密后应该恢复原文", plaintext, decrypted);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAesWithChineseContent() {
|
||||
System.out.println("\n[测试] AES加密中文内容");
|
||||
|
||||
String plaintext = "这是一段中文内容123";
|
||||
String key = "1234567890123456";
|
||||
String iv = "abcdefghijklmnop";
|
||||
|
||||
// ECB模式
|
||||
String encryptedEcb = cryptoUtils.aes_encrypt_ecb(plaintext, key);
|
||||
String decryptedEcb = cryptoUtils.aes_decrypt_ecb(encryptedEcb, key);
|
||||
assertEquals("ECB解密中文应该正确", plaintext, decryptedEcb);
|
||||
|
||||
// CBC模式
|
||||
String encryptedCbc = cryptoUtils.aes_encrypt_cbc(plaintext, key, iv);
|
||||
String decryptedCbc = cryptoUtils.aes_decrypt_cbc(encryptedCbc, key, iv);
|
||||
assertEquals("CBC解密中文应该正确", plaintext, decryptedCbc);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAesEcbCbcDifference() {
|
||||
System.out.println("\n[测试] AES ECB和CBC模式差异");
|
||||
|
||||
String plaintext = "hello world 1234";
|
||||
String key = "1234567890123456";
|
||||
String iv = "abcdefghijklmnop";
|
||||
|
||||
String ecbEncrypted = cryptoUtils.aes_encrypt_ecb(plaintext, key);
|
||||
String cbcEncrypted = cryptoUtils.aes_encrypt_cbc(plaintext, key, iv);
|
||||
|
||||
System.out.println("ECB密文: " + ecbEncrypted);
|
||||
System.out.println("CBC密文: " + cbcEncrypted);
|
||||
|
||||
// ECB和CBC模式的加密结果应该不同
|
||||
assertNotEquals("ECB和CBC加密结果应该不同", ecbEncrypted, cbcEncrypted);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
// ===================== 工具方法测试 =====================
|
||||
|
||||
@Test
|
||||
public void testBytesToHex() {
|
||||
System.out.println("\n[测试] 字节转十六进制");
|
||||
|
||||
byte[] input = {0x00, 0x0F, (byte) 0xFF, 0x10, (byte) 0xAB};
|
||||
String expected = "000fff10ab";
|
||||
|
||||
String result = cryptoUtils.bytes_to_hex(input);
|
||||
|
||||
System.out.println("输入字节: " + input.length + " 字节");
|
||||
System.out.println("十六进制: " + result);
|
||||
|
||||
assertEquals("字节转十六进制应该正确", expected, result);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsistencyWithJsCryptoUtils() {
|
||||
System.out.println("\n[测试] 与JavaScript加密工具一致性");
|
||||
|
||||
// 这些值应该与JsCryptoUtils产生相同的结果
|
||||
String testString = "consistency_test";
|
||||
|
||||
String md5 = cryptoUtils.md5(testString);
|
||||
String sha1 = cryptoUtils.sha1(testString);
|
||||
String sha256 = cryptoUtils.sha256(testString);
|
||||
String base64 = cryptoUtils.base64_encode(testString);
|
||||
|
||||
System.out.println("测试字符串: " + testString);
|
||||
System.out.println("MD5: " + md5);
|
||||
System.out.println("SHA1: " + sha1);
|
||||
System.out.println("SHA256: " + sha256);
|
||||
System.out.println("Base64: " + base64);
|
||||
|
||||
// 验证结果非空且格式正确
|
||||
assertNotNull("MD5不能为null", md5);
|
||||
assertEquals("MD5长度应该是32", 32, md5.length());
|
||||
|
||||
assertNotNull("SHA1不能为null", sha1);
|
||||
assertEquals("SHA1长度应该是40", 40, sha1.length());
|
||||
|
||||
assertNotNull("SHA256不能为null", sha256);
|
||||
assertEquals("SHA256长度应该是64", 64, sha256.length());
|
||||
|
||||
assertNotNull("Base64不能为null", base64);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullInput() {
|
||||
System.out.println("\n[测试] 空输入处理");
|
||||
|
||||
try {
|
||||
// MD5应该能处理null(返回null或抛出异常)
|
||||
String result = cryptoUtils.md5(null);
|
||||
// 如果没有抛出异常,结果应该是null
|
||||
System.out.println("MD5(null) = " + result);
|
||||
} catch (Exception e) {
|
||||
System.out.println("MD5(null) 抛出异常: " + e.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
System.out.println("✓ 空输入处理测试完成");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecialCharacters() {
|
||||
System.out.println("\n[测试] 特殊字符处理");
|
||||
|
||||
String[] testCases = {
|
||||
"~!@#$%^&*()_+",
|
||||
"日本語テスト",
|
||||
"🎉🎊🎁",
|
||||
"\n\t\r",
|
||||
" "
|
||||
};
|
||||
|
||||
for (String input : testCases) {
|
||||
String md5 = cryptoUtils.md5(input);
|
||||
String base64 = cryptoUtils.base64_encode(input);
|
||||
String decoded = cryptoUtils.base64_decode(base64);
|
||||
|
||||
assertNotNull("MD5不能为null", md5);
|
||||
assertEquals("Base64往返应该正确", input, decoded);
|
||||
}
|
||||
|
||||
System.out.println("✓ 测试通过(" + testCases.length + " 个测试用例)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
package cn.qaiu.parser;
|
||||
|
||||
import cn.qaiu.WebClientVertxInit;
|
||||
import cn.qaiu.parser.custompy.PyHttpClient;
|
||||
import io.vertx.core.Vertx;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* PyHttpClient 测试类
|
||||
* 测试Python HTTP客户端功能是否正常
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* Create at 2026/1/11
|
||||
*/
|
||||
public class PyHttpClientTest {
|
||||
|
||||
private static Vertx vertx;
|
||||
private PyHttpClient httpClient;
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
// 初始化Vertx
|
||||
vertx = Vertx.vertx();
|
||||
WebClientVertxInit.init(vertx);
|
||||
System.out.println("=== PyHttpClient测试初始化完成 ===\n");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// 创建PyHttpClient实例
|
||||
httpClient = new PyHttpClient();
|
||||
System.out.println("--- 测试开始 ---");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
System.out.println("--- 测试结束 ---\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleGetRequest() {
|
||||
System.out.println("\n[测试1] 简单GET请求 - httpbin.org/get");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/get";
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get(url);
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
System.out.println("请求完成,耗时: " + (endTime - startTime) + "ms");
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
String body = response.text();
|
||||
System.out.println("响应体长度: " + (body != null ? body.length() : 0) + " 字符");
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
assertTrue("请求应该成功", response.ok());
|
||||
assertNotNull("响应体不能为null", body);
|
||||
assertTrue("响应体应该包含url字段", body.contains("\"url\""));
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("GET请求失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWithRedirect() {
|
||||
System.out.println("\n[测试2] GET请求(跟随重定向)");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/redirect/1";
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get_with_redirect(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200(重定向后)", 200, response.status_code());
|
||||
assertTrue("请求应该成功", response.ok());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("GET重定向请求失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNoRedirect() {
|
||||
System.out.println("\n[测试3] GET请求(不跟随重定向)");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/redirect/1";
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get_no_redirect(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
String location = response.header("Location");
|
||||
System.out.println("Location头: " + location);
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertTrue("状态码应该是3xx重定向",
|
||||
response.status_code() >= 300 && response.status_code() < 400);
|
||||
assertFalse("ok()应该返回false", response.ok());
|
||||
assertNotNull("应该有Location头", location);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("GET不重定向请求失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostFormData() {
|
||||
System.out.println("\n[测试4] POST表单数据");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/post";
|
||||
Map<String, String> formData = new HashMap<>();
|
||||
formData.put("username", "testuser");
|
||||
formData.put("password", "testpass");
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
System.out.println("表单数据: " + formData);
|
||||
|
||||
PyHttpClient.PyHttpResponse response = httpClient.post(url, formData);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
String body = response.text();
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
assertTrue("响应体应该包含username", body.contains("testuser"));
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("POST表单数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostJson() {
|
||||
System.out.println("\n[测试5] POST JSON数据");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/post";
|
||||
Map<String, Object> jsonData = new HashMap<>();
|
||||
jsonData.put("name", "测试用户");
|
||||
jsonData.put("age", 25);
|
||||
jsonData.put("active", true);
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
System.out.println("JSON数据: " + jsonData);
|
||||
|
||||
PyHttpClient.PyHttpResponse response = httpClient.post_json(url, jsonData);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
String body = response.text();
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
assertTrue("响应体应该包含json数据", body.contains("测试用户") || body.contains("name"));
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("POST JSON数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomHeaders() {
|
||||
System.out.println("\n[测试6] 自定义请求头");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/headers";
|
||||
|
||||
// 设置自定义请求头
|
||||
httpClient.put_header("X-Custom-Header", "CustomValue")
|
||||
.put_header("X-Another-Header", "AnotherValue");
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
String body = response.text();
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
assertTrue("响应体应该包含自定义头", body.contains("X-Custom-Header"));
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("自定义请求头测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchHeaders() {
|
||||
System.out.println("\n[测试7] 批量设置请求头");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/headers";
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("X-Header-1", "Value1");
|
||||
headers.put("X-Header-2", "Value2");
|
||||
headers.put("X-Header-3", "Value3");
|
||||
|
||||
// 先清除之前的头
|
||||
httpClient.clear_headers();
|
||||
httpClient.put_headers(headers);
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
System.out.println("批量设置 " + headers.size() + " 个请求头");
|
||||
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("批量设置请求头测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseJson() {
|
||||
System.out.println("\n[测试8] 解析JSON响应");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/json";
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
// 清除之前设置的头
|
||||
httpClient.clear_headers();
|
||||
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
Object jsonObj = response.json();
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
assertNotNull("JSON对象不能为null", jsonObj);
|
||||
|
||||
System.out.println("JSON类型: " + jsonObj.getClass().getSimpleName());
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("解析JSON响应失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseHeader() {
|
||||
System.out.println("\n[测试9] 获取响应头");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/response-headers?X-Test-Header=TestValue";
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
httpClient.clear_headers();
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
String contentType = response.header("Content-Type");
|
||||
System.out.println("Content-Type: " + contentType);
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
assertNotNull("应该有Content-Type头", contentType);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("获取响应头失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContentLength() {
|
||||
System.out.println("\n[测试10] 获取内容长度");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/bytes/1024";
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
httpClient.clear_headers();
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
long contentLength = response.content_length();
|
||||
System.out.println("Content-Length: " + contentLength);
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
assertTrue("内容长度应该大于0", contentLength > 0);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("获取内容长度失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutRequest() {
|
||||
System.out.println("\n[测试11] PUT请求");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/put";
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("key", "value");
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
httpClient.clear_headers();
|
||||
PyHttpClient.PyHttpResponse response = httpClient.put(url, data);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("PUT请求失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteRequest() {
|
||||
System.out.println("\n[测试12] DELETE请求");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/delete";
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
httpClient.clear_headers();
|
||||
PyHttpClient.PyHttpResponse response = httpClient.delete(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("DELETE请求失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchRequest() {
|
||||
System.out.println("\n[测试13] PATCH请求");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/patch";
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("field", "updated");
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
httpClient.clear_headers();
|
||||
PyHttpClient.PyHttpResponse response = httpClient.patch(url, data);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("PATCH请求失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodChaining() {
|
||||
System.out.println("\n[测试14] 方法链式调用");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/headers";
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
// 测试链式调用
|
||||
PyHttpClient.PyHttpResponse response = new PyHttpClient()
|
||||
.put_header("X-Chain-1", "Value1")
|
||||
.put_header("X-Chain-2", "Value2")
|
||||
.set_timeout(30)
|
||||
.get(url);
|
||||
|
||||
System.out.println("状态码: " + response.status_code());
|
||||
|
||||
String body = response.text();
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.status_code());
|
||||
assertTrue("响应体应该包含链式设置的头", body.contains("X-Chain"));
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("方法链式调用测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBodyAndTextEquivalent() {
|
||||
System.out.println("\n[测试15] body()和text()方法等价性");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/get";
|
||||
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
httpClient.clear_headers();
|
||||
PyHttpClient.PyHttpResponse response = httpClient.get(url);
|
||||
|
||||
String body = response.body();
|
||||
String text = response.text();
|
||||
|
||||
// 验证结果
|
||||
assertEquals("body()和text()应该返回相同的结果", body, text);
|
||||
|
||||
System.out.println("✓ 测试通过");
|
||||
System.out.println(" body() == text(): " + body.equals(text));
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("body()和text()等价性测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
package cn.qaiu.parser;
|
||||
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import cn.qaiu.parser.custom.CustomParserRegistry;
|
||||
import cn.qaiu.parser.custompy.*;
|
||||
import cn.qaiu.WebClientVertxInit;
|
||||
import io.vertx.core.Vertx;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Python解析器测试
|
||||
* 测试GraalPy Python解析器的核心功能
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* Create at 2026/1/11
|
||||
*/
|
||||
public class PyParserTest {
|
||||
|
||||
private static Vertx vertx;
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
// 初始化Vertx
|
||||
vertx = Vertx.vertx();
|
||||
WebClientVertxInit.init(vertx);
|
||||
System.out.println("=== Python解析器测试初始化完成 ===\n");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// 清理注册表
|
||||
CustomParserRegistry.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPyContextPoolInitialization() {
|
||||
System.out.println("\n[测试] Context池初始化");
|
||||
|
||||
try {
|
||||
PyContextPool pool = PyContextPool.getInstance();
|
||||
|
||||
assertNotNull("Context池实例不能为null", pool);
|
||||
assertFalse("Context池不应该是关闭状态", pool.isClosed());
|
||||
assertTrue("应该有可用的Context", pool.getCreatedCount() > 0);
|
||||
|
||||
System.out.println("✓ Context池初始化测试通过");
|
||||
System.out.println(" " + pool.getStatus());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Context池初始化测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Context池初始化失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPyContextPoolAcquireRelease() throws Exception {
|
||||
System.out.println("\n[测试] Context池获取和释放");
|
||||
|
||||
try {
|
||||
PyContextPool pool = PyContextPool.getInstance();
|
||||
|
||||
// 获取Context
|
||||
PyContextPool.PooledContext pc = pool.acquire();
|
||||
assertNotNull("获取的Context不能为null", pc);
|
||||
assertNotNull("底层Context不能为null", pc.getContext());
|
||||
assertFalse("Context不应该过期", pc.isExpired());
|
||||
|
||||
int availableBefore = pool.getAvailableCount();
|
||||
|
||||
// 释放Context
|
||||
pc.close();
|
||||
|
||||
// 验证归还后可用数量增加
|
||||
int availableAfter = pool.getAvailableCount();
|
||||
assertTrue("归还后可用数量应该增加", availableAfter >= availableBefore);
|
||||
|
||||
System.out.println("✓ Context池获取和释放测试通过");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Context池获取和释放测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimplePythonExecution() {
|
||||
System.out.println("\n[测试] 简单Python代码执行");
|
||||
|
||||
String pyCode = """
|
||||
# 简单测试
|
||||
def parse(share_link_info, http, logger):
|
||||
logger.info("测试日志")
|
||||
return "https://example.com/download/test.zip"
|
||||
""";
|
||||
|
||||
try {
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/test123")
|
||||
.shareKey("test123")
|
||||
.otherParam(new HashMap<>())
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
String result = executor.executeParseAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
|
||||
assertNotNull("执行结果不能为null", result);
|
||||
assertTrue("应该返回下载链接", result.contains("example.com"));
|
||||
|
||||
// 检查日志
|
||||
List<PyPlaygroundLogger.LogEntry> logs = executor.getLogs();
|
||||
assertFalse("应该有日志输出", logs.isEmpty());
|
||||
|
||||
System.out.println("✓ 简单Python代码执行测试通过");
|
||||
System.out.println(" 返回结果: " + result);
|
||||
System.out.println(" 日志数量: " + logs.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 简单Python代码执行测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Python执行失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonHttpRequest() {
|
||||
System.out.println("\n[测试] Python HTTP请求功能");
|
||||
|
||||
String pyCode = """
|
||||
def parse(share_link_info, http, logger):
|
||||
logger.info("开始HTTP请求测试")
|
||||
|
||||
# 发送GET请求
|
||||
response = http.get("https://httpbin.org/get")
|
||||
|
||||
if response.ok():
|
||||
logger.info(f"请求成功,状态码: {response.status_code()}")
|
||||
return "https://example.com/success"
|
||||
else:
|
||||
logger.error(f"请求失败,状态码: {response.status_code()}")
|
||||
return "https://example.com/failed"
|
||||
""";
|
||||
|
||||
try {
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/test123")
|
||||
.shareKey("test123")
|
||||
.otherParam(new HashMap<>())
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
String result = executor.executeParseAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(60, TimeUnit.SECONDS);
|
||||
|
||||
assertNotNull("执行结果不能为null", result);
|
||||
assertTrue("应该返回成功链接", result.contains("success"));
|
||||
|
||||
System.out.println("✓ Python HTTP请求功能测试通过");
|
||||
System.out.println(" 返回结果: " + result);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Python HTTP请求功能测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Python HTTP请求失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonCryptoUtils() {
|
||||
System.out.println("\n[测试] Python加密工具功能");
|
||||
|
||||
String pyCode = """
|
||||
def parse(share_link_info, http, logger):
|
||||
# 测试MD5
|
||||
md5_result = crypto.md5("hello")
|
||||
logger.info(f"MD5: {md5_result}")
|
||||
|
||||
# 测试SHA256
|
||||
sha256_result = crypto.sha256("hello")
|
||||
logger.info(f"SHA256: {sha256_result}")
|
||||
|
||||
# 测试Base64编码解码
|
||||
b64_encoded = crypto.base64_encode("hello world")
|
||||
b64_decoded = crypto.base64_decode(b64_encoded)
|
||||
logger.info(f"Base64: {b64_encoded} -> {b64_decoded}")
|
||||
|
||||
# 验证MD5正确性
|
||||
if md5_result == "5d41402abc4b2a76b9719d911017c592":
|
||||
return "https://example.com/crypto_success"
|
||||
else:
|
||||
return "https://example.com/crypto_failed"
|
||||
""";
|
||||
|
||||
try {
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/test123")
|
||||
.shareKey("test123")
|
||||
.otherParam(new HashMap<>())
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
String result = executor.executeParseAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
|
||||
assertNotNull("执行结果不能为null", result);
|
||||
assertTrue("加密工具应该正常工作", result.contains("crypto_success"));
|
||||
|
||||
System.out.println("✓ Python加密工具功能测试通过");
|
||||
System.out.println(" 返回结果: " + result);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Python加密工具功能测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Python加密工具测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonShareLinkInfo() {
|
||||
System.out.println("\n[测试] Python ShareLinkInfo访问");
|
||||
|
||||
String pyCode = """
|
||||
def parse(share_link_info, http, logger):
|
||||
# 获取分享链接信息
|
||||
url = share_link_info.get_share_url()
|
||||
key = share_link_info.get_share_key()
|
||||
pwd = share_link_info.get_share_password()
|
||||
|
||||
logger.info(f"URL: {url}")
|
||||
logger.info(f"Key: {key}")
|
||||
logger.info(f"Password: {pwd}")
|
||||
|
||||
# 测试其他参数
|
||||
custom_param = share_link_info.get_other_param("customKey")
|
||||
logger.info(f"CustomKey: {custom_param}")
|
||||
|
||||
if url and key:
|
||||
return f"https://example.com/download/{key}"
|
||||
else:
|
||||
return "https://example.com/failed"
|
||||
""";
|
||||
|
||||
try {
|
||||
Map<String, Object> otherParams = new HashMap<>();
|
||||
otherParams.put("customKey", "customValue");
|
||||
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/mykey123")
|
||||
.shareKey("mykey123")
|
||||
.sharePassword("mypassword")
|
||||
.otherParam(otherParams)
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
String result = executor.executeParseAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
|
||||
assertNotNull("执行结果不能为null", result);
|
||||
assertTrue("应该包含正确的key", result.contains("mykey123"));
|
||||
|
||||
System.out.println("✓ Python ShareLinkInfo访问测试通过");
|
||||
System.out.println(" 返回结果: " + result);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Python ShareLinkInfo访问测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Python ShareLinkInfo访问失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonFileListParsing() {
|
||||
System.out.println("\n[测试] Python文件列表解析");
|
||||
|
||||
String pyCode = """
|
||||
def parse(share_link_info, http, logger):
|
||||
return "https://example.com/download/single.zip"
|
||||
|
||||
def parse_file_list(share_link_info, http, logger):
|
||||
logger.info("开始解析文件列表")
|
||||
|
||||
# 返回文件列表
|
||||
file_list = [
|
||||
{
|
||||
"file_name": "测试文件1.txt",
|
||||
"file_id": "file001",
|
||||
"file_type": "txt",
|
||||
"size": 1024,
|
||||
"pan_type": "custom"
|
||||
},
|
||||
{
|
||||
"file_name": "测试文件2.zip",
|
||||
"file_id": "file002",
|
||||
"file_type": "zip",
|
||||
"size": 2048,
|
||||
"pan_type": "custom"
|
||||
}
|
||||
]
|
||||
|
||||
logger.info(f"解析到 {len(file_list)} 个文件")
|
||||
return file_list
|
||||
""";
|
||||
|
||||
try {
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/test123")
|
||||
.shareKey("test123")
|
||||
.otherParam(new HashMap<>())
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
List<FileInfo> fileList = executor.executeParseFileListAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
|
||||
assertNotNull("文件列表不能为null", fileList);
|
||||
assertEquals("应该有2个文件", 2, fileList.size());
|
||||
|
||||
FileInfo firstFile = fileList.get(0);
|
||||
assertEquals("第一个文件名应该正确", "测试文件1.txt", firstFile.getFileName());
|
||||
assertEquals("第一个文件ID应该正确", "file001", firstFile.getFileId());
|
||||
|
||||
System.out.println("✓ Python文件列表解析测试通过");
|
||||
System.out.println(" 文件数量: " + fileList.size());
|
||||
for (FileInfo file : fileList) {
|
||||
System.out.println(" - " + file.getFileName() + " (" + file.getSize() + " bytes)");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Python文件列表解析测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Python文件列表解析失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonParseById() {
|
||||
System.out.println("\n[测试] Python按ID解析");
|
||||
|
||||
String pyCode = """
|
||||
def parse(share_link_info, http, logger):
|
||||
return "https://example.com/download/single.zip"
|
||||
|
||||
def parse_by_id(share_link_info, http, logger):
|
||||
# 获取文件ID参数
|
||||
param_json = share_link_info.get_other_param("paramJson")
|
||||
|
||||
if param_json and hasattr(param_json, 'fileId'):
|
||||
file_id = param_json.fileId
|
||||
else:
|
||||
file_id = "default_id"
|
||||
|
||||
logger.info(f"按ID解析: {file_id}")
|
||||
return f"https://example.com/download/{file_id}"
|
||||
""";
|
||||
|
||||
try {
|
||||
Map<String, Object> otherParams = new HashMap<>();
|
||||
io.vertx.core.json.JsonObject paramJson = new io.vertx.core.json.JsonObject();
|
||||
paramJson.put("fileId", "myfile123");
|
||||
otherParams.put("paramJson", paramJson);
|
||||
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/test123")
|
||||
.shareKey("test123")
|
||||
.otherParam(otherParams)
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
String result = executor.executeParseByIdAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
|
||||
assertNotNull("执行结果不能为null", result);
|
||||
assertTrue("应该包含文件ID", result.contains("download"));
|
||||
|
||||
System.out.println("✓ Python按ID解析测试通过");
|
||||
System.out.println(" 返回结果: " + result);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Python按ID解析测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Python按ID解析失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonErrorHandling() {
|
||||
System.out.println("\n[测试] Python错误处理");
|
||||
|
||||
String pyCode = """
|
||||
def parse(share_link_info, http, logger):
|
||||
# 故意抛出异常
|
||||
raise ValueError("测试错误处理")
|
||||
""";
|
||||
|
||||
try {
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/test123")
|
||||
.shareKey("test123")
|
||||
.otherParam(new HashMap<>())
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
try {
|
||||
executor.executeParseAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
|
||||
fail("应该抛出异常");
|
||||
|
||||
} catch (Exception e) {
|
||||
// 预期的异常
|
||||
assertTrue("异常信息应该包含错误内容",
|
||||
e.getMessage().contains("ValueError") ||
|
||||
e.getCause().getMessage().contains("ValueError"));
|
||||
|
||||
System.out.println("✓ Python错误处理测试通过");
|
||||
System.out.println(" 捕获到预期的异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Python错误处理测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Python错误处理测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonSandboxSecurity() {
|
||||
System.out.println("\n[测试] Python沙箱安全性");
|
||||
|
||||
// 测试禁止文件系统访问
|
||||
String pyCode = """
|
||||
import os
|
||||
|
||||
def parse(share_link_info, http, logger):
|
||||
try:
|
||||
# 尝试读取文件(应该被拒绝)
|
||||
with open("/etc/passwd", "r") as f:
|
||||
content = f.read()
|
||||
return "https://example.com/security_breach"
|
||||
except Exception as e:
|
||||
logger.info(f"文件访问被正确拒绝: {type(e).__name__}")
|
||||
return "https://example.com/security_ok"
|
||||
""";
|
||||
|
||||
try {
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/test123")
|
||||
.shareKey("test123")
|
||||
.otherParam(new HashMap<>())
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
String result = executor.executeParseAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
|
||||
// 如果返回security_ok或抛出异常都表示安全机制工作正常
|
||||
assertTrue("沙箱应该阻止文件访问",
|
||||
result.contains("security_ok") || !result.contains("security_breach"));
|
||||
|
||||
System.out.println("✓ Python沙箱安全性测试通过");
|
||||
System.out.println(" 返回结果: " + result);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 如果直接抛出异常也表示安全机制工作正常
|
||||
System.out.println("✓ Python沙箱安全性测试通过(抛出异常)");
|
||||
System.out.println(" 异常信息: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPythonLoggerLevels() {
|
||||
System.out.println("\n[测试] Python日志级别");
|
||||
|
||||
String pyCode = """
|
||||
def parse(share_link_info, http, logger):
|
||||
logger.debug("这是DEBUG日志")
|
||||
logger.info("这是INFO日志")
|
||||
logger.warn("这是WARN日志")
|
||||
logger.error("这是ERROR日志")
|
||||
return "https://example.com/log_test"
|
||||
""";
|
||||
|
||||
try {
|
||||
ShareLinkInfo linkInfo = ShareLinkInfo.newBuilder()
|
||||
.shareUrl("https://example.com/s/test123")
|
||||
.shareKey("test123")
|
||||
.otherParam(new HashMap<>())
|
||||
.build();
|
||||
|
||||
PyPlaygroundExecutor executor = new PyPlaygroundExecutor(linkInfo, pyCode);
|
||||
|
||||
executor.executeParseAsync()
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(30, TimeUnit.SECONDS);
|
||||
|
||||
List<PyPlaygroundLogger.LogEntry> logs = executor.getLogs();
|
||||
|
||||
// 检查各个级别的日志
|
||||
boolean hasDebug = logs.stream().anyMatch(l -> "DEBUG".equals(l.getLevel()));
|
||||
boolean hasInfo = logs.stream().anyMatch(l -> "INFO".equals(l.getLevel()));
|
||||
boolean hasWarn = logs.stream().anyMatch(l -> "WARN".equals(l.getLevel()));
|
||||
boolean hasError = logs.stream().anyMatch(l -> "ERROR".equals(l.getLevel()));
|
||||
|
||||
System.out.println("✓ Python日志级别测试通过");
|
||||
System.out.println(" 日志数量: " + logs.size());
|
||||
System.out.println(" DEBUG: " + hasDebug);
|
||||
System.out.println(" INFO: " + hasInfo);
|
||||
System.out.println(" WARN: " + hasWarn);
|
||||
System.out.println(" ERROR: " + hasError);
|
||||
|
||||
for (PyPlaygroundLogger.LogEntry log : logs) {
|
||||
System.out.println(" [" + log.getLevel() + "] " + log.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ Python日志级别测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("Python日志级别测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user