|
| 1 | + |
| 2 | +import * as drpyS from '../../libs/drpyS.js'; |
| 3 | +import path from 'path'; |
| 4 | +import {fileURLToPath} from 'url'; |
| 5 | +import {existsSync} from 'fs'; |
| 6 | + |
| 7 | +const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 8 | + |
| 9 | +// Mock server constants |
| 10 | +const PORT = 5705; |
| 11 | +const WsPORT = 5706; |
| 12 | +const protocol = 'http'; |
| 13 | +const hostname = 'localhost:5705'; |
| 14 | + |
| 15 | +function getEnv(moduleName, query = {}) { |
| 16 | + const moduleExt = query.extend || ''; |
| 17 | + const requestHost = `${protocol}://${hostname}`; |
| 18 | + const publicUrl = `${protocol}://${hostname}/public/`; |
| 19 | + const jsonUrl = `${protocol}://${hostname}/json/`; |
| 20 | + const httpUrl = `${protocol}://${hostname}/http`; |
| 21 | + const imageApi = `${protocol}://${hostname}/image`; |
| 22 | + const mediaProxyUrl = `${protocol}://${hostname}/mediaProxy`; |
| 23 | + const webdavProxyUrl = `${protocol}://${hostname}/webdav/`; |
| 24 | + const ftpProxyUrl = `${protocol}://${hostname}/ftp/`; |
| 25 | + const hostUrl = `${hostname.split(':')[0]}`; |
| 26 | + const wsName = hostname.replace(`:${PORT}`, `:${WsPORT}`); |
| 27 | + const fServer = null; |
| 28 | + |
| 29 | + const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=${query.do || 'ds'}&extend=${encodeURIComponent(moduleExt)}`; |
| 30 | + const getProxyUrl = function () { |
| 31 | + return proxyUrl |
| 32 | + }; |
| 33 | + |
| 34 | + const env = { |
| 35 | + requestHost, |
| 36 | + proxyUrl, |
| 37 | + publicUrl, |
| 38 | + jsonUrl, |
| 39 | + httpUrl, |
| 40 | + imageApi, |
| 41 | + mediaProxyUrl, |
| 42 | + webdavProxyUrl, |
| 43 | + ftpProxyUrl, |
| 44 | + hostUrl, |
| 45 | + hostname, |
| 46 | + wsName, |
| 47 | + fServer, |
| 48 | + getProxyUrl, |
| 49 | + ext: moduleExt |
| 50 | + }; |
| 51 | + |
| 52 | + env.getRule = async function (_moduleName) { |
| 53 | + const _modulePath = path.join(__dirname, '../js', `${_moduleName}.js`); |
| 54 | + if (!existsSync(_modulePath)) { |
| 55 | + return null; |
| 56 | + } |
| 57 | + const _env = getEnv(_moduleName); |
| 58 | + const RULE = await drpyS.getRuleObject(_modulePath, _env); |
| 59 | + |
| 60 | + RULE.callRuleFn = async function (_method, _args) { |
| 61 | + let invokeMethod = null; |
| 62 | + switch (_method) { |
| 63 | + case 'class_parse': invokeMethod = 'home'; break; |
| 64 | + case '推荐': invokeMethod = 'homeVod'; break; |
| 65 | + case '一级': invokeMethod = 'category'; break; |
| 66 | + case '二级': invokeMethod = 'detail'; break; |
| 67 | + case '搜索': invokeMethod = 'search'; break; |
| 68 | + case 'lazy': invokeMethod = 'play'; break; |
| 69 | + case 'proxy_rule': invokeMethod = 'proxy'; break; |
| 70 | + case 'action': invokeMethod = 'action'; break; |
| 71 | + } |
| 72 | + |
| 73 | + if (!invokeMethod) { |
| 74 | + if (typeof RULE[_method] !== 'function') { |
| 75 | + return null |
| 76 | + } else { |
| 77 | + return await RULE[_method](..._args); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + return await drpyS[invokeMethod](_modulePath, _env, ..._args); |
| 82 | + }; |
| 83 | + |
| 84 | + return RULE; |
| 85 | + } |
| 86 | + |
| 87 | + return env; |
| 88 | +} |
| 89 | + |
| 90 | +// Test Status Reporter |
| 91 | +const stats = { |
| 92 | + results: [], |
| 93 | + pass(name) { this.results.push({name, status: 'PASS'}); }, |
| 94 | + fail(name, err) { this.results.push({name, status: 'FAIL', error: err}); }, |
| 95 | + skip(name, reason) { this.results.push({name, status: 'SKIP', reason}); }, |
| 96 | + summary() { |
| 97 | + const total = this.results.length; |
| 98 | + const passed = this.results.filter(r => r.status === 'PASS').length; |
| 99 | + const failed = this.results.filter(r => r.status === 'FAIL').length; |
| 100 | + const skipped = this.results.filter(r => r.status === 'SKIP').length; |
| 101 | + |
| 102 | + console.log('\n\n=============================================='); |
| 103 | + console.log(' TEST SUMMARY REPORT '); |
| 104 | + console.log('=============================================='); |
| 105 | + console.log(`Total Steps : ${total}`); |
| 106 | + console.log(`Passed : ${passed}`); |
| 107 | + console.log(`Failed : ${failed}`); |
| 108 | + console.log(`Skipped : ${skipped}`); |
| 109 | + console.log('----------------------------------------------'); |
| 110 | + |
| 111 | + this.results.forEach(r => { |
| 112 | + let statusIcon = r.status === 'PASS' ? '✅' : (r.status === 'FAIL' ? '❌' : '⚠️'); |
| 113 | + let msg = `${statusIcon} [${r.status}] ${r.name}`; |
| 114 | + if (r.error) msg += ` - Error: ${r.error}`; |
| 115 | + if (r.reason) msg += ` - Reason: ${r.reason}`; |
| 116 | + console.log(msg); |
| 117 | + }); |
| 118 | + console.log('==============================================\n'); |
| 119 | + } |
| 120 | +}; |
| 121 | + |
| 122 | +(async () => { |
| 123 | + // 设置目标模块路径 |
| 124 | + const moduleName = '七猫小说[书]'; |
| 125 | + const modulePath = path.join(__dirname, `../js/${moduleName}.js`); |
| 126 | + |
| 127 | + // Create environment |
| 128 | + const env = getEnv(moduleName); |
| 129 | + |
| 130 | + // Shared variables for test dependencies |
| 131 | + let homeResult; |
| 132 | + let cateResult; |
| 133 | + let searchResult; |
| 134 | + let detailResult; |
| 135 | + let detailUrl = ''; |
| 136 | + |
| 137 | + try { |
| 138 | + console.log('Initializing module...'); |
| 139 | + await drpyS.init(modulePath); |
| 140 | + |
| 141 | + // 1. 测试 Home/Category (分类列表) |
| 142 | + try { |
| 143 | + console.log('\n=== Testing Home/Category ==='); |
| 144 | + homeResult = await drpyS.home(modulePath, env); |
| 145 | + console.log('Home Result:', JSON.stringify(homeResult.class.slice(0, 3))); // 只打印前3个分类 |
| 146 | + |
| 147 | + if (homeResult && homeResult.class && homeResult.class.length > 0) { |
| 148 | + stats.pass('Home/Category'); |
| 149 | + } else { |
| 150 | + throw new Error('No classes returned'); |
| 151 | + } |
| 152 | + } catch (e) { |
| 153 | + stats.fail('Home/Category', e.message); |
| 154 | + // If home fails, we can't test category, but maybe search works? |
| 155 | + } |
| 156 | + |
| 157 | + // 2. 测试 Category Content (一级 - 分类内容) |
| 158 | + if (homeResult && homeResult.class && homeResult.class.length > 0) { |
| 159 | + try { |
| 160 | + // 使用第一个分类的 ID |
| 161 | + const classId = homeResult.class[0].type_id; |
| 162 | + console.log(`\n=== Testing Category Content (class_id=${classId}) ===`); |
| 163 | + // category(filePath, env, tid, pg, filter, extend) |
| 164 | + cateResult = await drpyS.category(modulePath, env, classId, 1); |
| 165 | + console.log('Category Result Count:', cateResult.list.length); |
| 166 | + if (cateResult.list.length > 0) { |
| 167 | + console.log('First Item:', cateResult.list[0]); |
| 168 | + stats.pass('Category Content'); |
| 169 | + } else { |
| 170 | + stats.fail('Category Content', 'No items in category'); |
| 171 | + } |
| 172 | + } catch (e) { |
| 173 | + stats.fail('Category Content', e.message); |
| 174 | + } |
| 175 | + } else { |
| 176 | + stats.skip('Category Content', 'Dependency failed: Home/Category'); |
| 177 | + } |
| 178 | + |
| 179 | + // 3. 测试 Search (搜索) |
| 180 | + try { |
| 181 | + const keyword = '剑来'; |
| 182 | + console.log(`\n=== Testing Search (keyword=${keyword}) ===`); |
| 183 | + searchResult = await drpyS.search(modulePath, env, keyword); |
| 184 | + console.log('Search Result Count:', searchResult.list.length); |
| 185 | + if (searchResult.list.length > 0) { |
| 186 | + console.log('First Search Item:', searchResult.list[0]); |
| 187 | + stats.pass('Search'); |
| 188 | + } else { |
| 189 | + stats.fail('Search', 'No search results found'); |
| 190 | + } |
| 191 | + } catch (e) { |
| 192 | + stats.fail('Search', e.message); |
| 193 | + } |
| 194 | + |
| 195 | + // 4. 测试 Detail (二级 - 详情) |
| 196 | + // 优先使用搜索结果中的 URL (vod_id),如果没有则尝试分类结果 |
| 197 | + if (searchResult && searchResult.list && searchResult.list.length > 0) { |
| 198 | + detailUrl = searchResult.list[0].vod_id; |
| 199 | + } else if (cateResult && cateResult.list && cateResult.list.length > 0) { |
| 200 | + detailUrl = cateResult.list[0].vod_id; |
| 201 | + } |
| 202 | + |
| 203 | + if (detailUrl) { |
| 204 | + try { |
| 205 | + console.log(`\n=== Testing Detail (url=${detailUrl}) ===`); |
| 206 | + const detailOrList = await drpyS.detail(modulePath, env, [detailUrl]); |
| 207 | + |
| 208 | + if (detailOrList.list && Array.isArray(detailOrList.list)) { |
| 209 | + detailResult = detailOrList.list[0]; |
| 210 | + } else if (Array.isArray(detailOrList)) { |
| 211 | + detailResult = detailOrList[0]; |
| 212 | + } else { |
| 213 | + detailResult = detailOrList; |
| 214 | + } |
| 215 | + |
| 216 | + if (!detailResult) { |
| 217 | + throw new Error('Detail result is empty or invalid'); |
| 218 | + } |
| 219 | + |
| 220 | + console.log('Detail Result:', { |
| 221 | + vod_name: detailResult.vod_name, |
| 222 | + vod_play_from: detailResult.vod_play_from, |
| 223 | + // 截取 play_url 防止日志过长 |
| 224 | + vod_play_url: detailResult.vod_play_url ? (detailResult.vod_play_url.slice(0, 100) + '...') : 'N/A' |
| 225 | + }); |
| 226 | + stats.pass('Detail'); |
| 227 | + |
| 228 | + } catch (e) { |
| 229 | + stats.fail('Detail', e.message); |
| 230 | + } |
| 231 | + } else { |
| 232 | + stats.skip('Detail', 'No valid URL found from Search or Category results'); |
| 233 | + } |
| 234 | + |
| 235 | + // 5. 测试 Play (播放/阅读) |
| 236 | + if (detailResult && detailResult.vod_play_url) { |
| 237 | + try { |
| 238 | + console.log('\n=== Testing Play ==='); |
| 239 | + // 解析 vod_play_url 获取播放链接 |
| 240 | + const flags = detailResult.vod_play_from.split('$$$'); |
| 241 | + const urls = detailResult.vod_play_url.split('$$$'); |
| 242 | + |
| 243 | + // 取第一个播放源的第一个章节 |
| 244 | + const firstFlagUrlList = urls[0].split('#'); |
| 245 | + const firstChapter = firstFlagUrlList[0]; |
| 246 | + // 格式: "章节名$参数" -> "第一章$1747899@@123456@@第一章" |
| 247 | + const playUrl = firstChapter.split('$')[1]; |
| 248 | + |
| 249 | + console.log(`Playing Chapter: ${firstChapter.split('$')[0]}`); |
| 250 | + console.log(`Play URL Params: ${playUrl}`); |
| 251 | + |
| 252 | + const playResult = await drpyS.play(modulePath, env, flags[0], playUrl); |
| 253 | + |
| 254 | + const logResult = {...playResult}; |
| 255 | + if (logResult.url && logResult.url.startsWith('novel://')) { |
| 256 | + logResult.url = logResult.url.slice(0, 100) + '... (truncated content)'; |
| 257 | + } |
| 258 | + console.log('Play Result:', logResult); |
| 259 | + |
| 260 | + if (playResult.url || playResult.parse === 0) { |
| 261 | + stats.pass('Play'); |
| 262 | + } else { |
| 263 | + stats.fail('Play', 'No play URL or content returned'); |
| 264 | + } |
| 265 | + |
| 266 | + } catch (e) { |
| 267 | + stats.fail('Play', e.message); |
| 268 | + } |
| 269 | + } else { |
| 270 | + const reason = !detailResult ? 'Dependency failed: Detail' : 'No vod_play_url in detail'; |
| 271 | + stats.skip('Play', reason); |
| 272 | + } |
| 273 | + |
| 274 | + } catch (error) { |
| 275 | + console.error('Critical Error during test initialization:', error); |
| 276 | + } finally { |
| 277 | + stats.summary(); |
| 278 | + } |
| 279 | +})(); |
0 commit comments