@sovovs/bycli 1.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli-manifest.json +0 -61
- package/dist/src/adapter-shadow.js +2 -2
- package/dist/src/browser/analyze.d.ts +10 -99
- package/dist/src/browser/analyze.js +10 -226
- package/dist/src/browser/cdp.js +64 -7
- package/dist/src/browser/navigation-guard.d.ts +78 -0
- package/dist/src/browser/navigation-guard.js +142 -0
- package/dist/src/browser/navigation-guard.test.d.ts +1 -0
- package/dist/src/browser/url-policy.d.ts +89 -0
- package/dist/src/browser/url-policy.js +311 -0
- package/dist/src/browser/url-policy.test.d.ts +1 -0
- package/dist/src/browser/verify-fixture.js +2 -2
- package/dist/src/cli.js +10 -13
- package/dist/src/config-paths.d.ts +19 -0
- package/dist/src/config-paths.js +31 -0
- package/dist/src/daemon.js +240 -0
- package/dist/src/discovery.d.ts +5 -3
- package/dist/src/discovery.js +8 -6
- package/dist/src/execution.js +8 -3
- package/dist/src/main.js +18 -2
- package/dist/src/plugin.js +11 -8
- package/dist/src/recorder/highlevel/analyze.d.ts +29 -0
- package/dist/src/recorder/highlevel/analyze.js +91 -0
- package/dist/src/recorder/highlevel/init.d.ts +125 -0
- package/dist/src/recorder/highlevel/init.js +279 -0
- package/dist/src/recorder/highlevel/init.test.d.ts +1 -0
- package/dist/src/recorder/highlevel/verify.d.ts +73 -0
- package/dist/src/recorder/highlevel/verify.js +74 -0
- package/dist/src/recorder/http/analyze-runner.d.ts +28 -0
- package/dist/src/recorder/http/analyze-runner.js +78 -0
- package/dist/src/recorder/http/analyze-runner.test.d.ts +1 -0
- package/dist/src/recorder/http/handlers.d.ts +51 -0
- package/dist/src/recorder/http/handlers.js +219 -0
- package/dist/src/recorder/http/wrapper-config.d.ts +20 -0
- package/dist/src/recorder/http/wrapper-config.js +82 -0
- package/dist/src/recorder/http/wrapper-envelope.d.ts +31 -0
- package/dist/src/recorder/http/wrapper-envelope.js +51 -0
- package/dist/src/recorder/http/wrapper-gates.d.ts +16 -0
- package/dist/src/recorder/http/wrapper-gates.js +28 -0
- package/dist/src/recorder/http/wrapper-metrics.d.ts +1 -0
- package/dist/src/recorder/http/wrapper-metrics.js +5 -0
- package/dist/src/recorder/http/wrapper-registry.d.ts +50 -0
- package/dist/src/recorder/http/wrapper-registry.js +98 -0
- package/dist/src/recorder/http/wrapper-server.d.ts +59 -0
- package/dist/src/recorder/http/wrapper-server.js +240 -0
- package/dist/src/recorder/http/wrapper.test.d.ts +1 -0
- package/dist/src/recorder/observability/logger.d.ts +5 -0
- package/dist/src/recorder/observability/logger.js +13 -0
- package/dist/src/recorder/runner/config.d.ts +34 -0
- package/dist/src/recorder/runner/config.js +84 -0
- package/dist/src/recorder/runner/config.test.d.ts +1 -0
- package/dist/src/recorder/runner/reap.d.ts +79 -0
- package/dist/src/recorder/runner/reap.js +230 -0
- package/dist/src/recorder/runner/reap.test.d.ts +1 -0
- package/dist/src/recorder/runner/runner-port.d.ts +95 -0
- package/dist/src/recorder/runner/runner-port.js +518 -0
- package/dist/src/recorder/runner/runner-port.test.d.ts +1 -0
- package/dist/src/recorder/runner/session-keys.d.ts +28 -0
- package/dist/src/recorder/runner/session-keys.js +72 -0
- package/dist/src/recorder/runner/session-keys.test.d.ts +1 -0
- package/dist/src/recorder/runner/verify-runner-main.d.ts +95 -0
- package/dist/src/recorder/runner/verify-runner-main.js +275 -0
- package/dist/src/recorder/runner/verify-runner-main.test.d.ts +1 -0
- package/package.json +8 -5
- package/scripts/m1-daemon-navigate-drive.mjs +138 -0
- package/scripts/m1-extension-spike.mjs +237 -0
- package/scripts/m1-live-spike.mjs +156 -0
- package/scripts/m6b-connect-back-spike.sh +31 -0
- package/scripts/recorder.sh +186 -0
- package/clis/12306/utils.test.js +0 -331
- package/clis/1688/assets.test.js +0 -39
- package/clis/1688/download.test.js +0 -31
- package/clis/1688/item.test.js +0 -67
- package/clis/1688/search.test.js +0 -75
- package/clis/1688/shared.test.js +0 -57
- package/clis/1688/store.test.js +0 -62
- package/clis/36kr/article.test.js +0 -46
- package/clis/36kr/hot.test.js +0 -15
- package/clis/36kr/news.test.js +0 -85
- package/clis/51job/utils.test.js +0 -69
- package/clis/_atlassian/shared.test.js +0 -170
- package/clis/aibase/news.test.js +0 -59
- package/clis/amazon/bestsellers.test.js +0 -29
- package/clis/amazon/discussion.test.js +0 -124
- package/clis/amazon/offer.test.js +0 -29
- package/clis/amazon/product.test.js +0 -24
- package/clis/amazon/rankings.test.js +0 -41
- package/clis/amazon/search.test.js +0 -22
- package/clis/amazon/shared.test.js +0 -44
- package/clis/apple-podcasts/commands.test.js +0 -119
- package/clis/apple-podcasts/utils.test.js +0 -57
- package/clis/arxiv/arxiv.test.js +0 -112
- package/clis/baidu-scholar/search.test.js +0 -23
- package/clis/barchart/greeks.test.js +0 -138
- package/clis/bilibili/comment.test.js +0 -153
- package/clis/bilibili/comments.test.js +0 -142
- package/clis/bilibili/dynamic.test.js +0 -68
- package/clis/bilibili/hot.test.js +0 -17
- package/clis/bilibili/subtitle.test.js +0 -196
- package/clis/bilibili/summary.test.js +0 -210
- package/clis/bilibili/utils.test.js +0 -61
- package/clis/bilibili/video.test.js +0 -132
- package/clis/binance/commands.test.js +0 -70
- package/clis/bloomberg/utils.test.js +0 -129
- package/clis/booking/booking.test.js +0 -356
- package/clis/boss/chatlist.test.js +0 -211
- package/clis/boss/chatmsg.test.js +0 -230
- package/clis/boss/search.test.js +0 -78
- package/clis/boss/utils.test.js +0 -34
- package/clis/brave/search.test.js +0 -76
- package/clis/chaoxing/utils.test.js +0 -45
- package/clis/chatgpt/commands.test.js +0 -45
- package/clis/chatgpt/envelope.test.js +0 -108
- package/clis/chatgpt/image.test.js +0 -188
- package/clis/chatgpt/utils.test.js +0 -330
- package/clis/chatgpt-app/ax.test.js +0 -35
- package/clis/chatwise/composer.test.js +0 -186
- package/clis/chess/analyze.test.js +0 -79
- package/clis/chess/game.test.js +0 -178
- package/clis/chess/games.test.js +0 -164
- package/clis/chess/stats.test.js +0 -79
- package/clis/chess/utils.test.js +0 -230
- package/clis/claude/ask.test.js +0 -338
- package/clis/claude/commands.test.js +0 -118
- package/clis/claude/utils.test.js +0 -148
- package/clis/cnki/search.test.js +0 -18
- package/clis/codex/sidebar.test.js +0 -329
- package/clis/coingecko/coingecko.test.js +0 -109
- package/clis/confluence/commands.test.js +0 -195
- package/clis/coupang/coupang.test.js +0 -159
- package/clis/coupang/utils.test.js +0 -62
- package/clis/ctrip/ctrip.test.js +0 -719
- package/clis/dblp/dblp.test.js +0 -397
- package/clis/deepseek/ask.test.js +0 -312
- package/clis/deepseek/detail.test.js +0 -81
- package/clis/deepseek/send.test.js +0 -107
- package/clis/deepseek/utils.test.js +0 -370
- package/clis/defillama/defillama.test.js +0 -99
- package/clis/devto/devto.test.js +0 -236
- package/clis/dianping/dianping.test.js +0 -578
- package/clis/douban/download.test.js +0 -170
- package/clis/douban/movie-hot.test.js +0 -14
- package/clis/douban/search.test.js +0 -11
- package/clis/douban/subject.test.js +0 -11
- package/clis/douban/utils.test.js +0 -438
- package/clis/doubao/detail.test.js +0 -42
- package/clis/doubao/history.test.js +0 -37
- package/clis/doubao/utils.test.js +0 -344
- package/clis/douyin/_shared/browser-fetch.test.js +0 -64
- package/clis/douyin/_shared/creation-id.test.js +0 -22
- package/clis/douyin/_shared/imagex-upload.test.js +0 -87
- package/clis/douyin/_shared/sts2.test.js +0 -27
- package/clis/douyin/_shared/text-extra.test.js +0 -37
- package/clis/douyin/_shared/timing.test.js +0 -28
- package/clis/douyin/_shared/tos-upload-short-read.test.js +0 -83
- package/clis/douyin/_shared/tos-upload.test.js +0 -229
- package/clis/douyin/_shared/transcode.test.js +0 -91
- package/clis/douyin/_shared/vod-upload.test.js +0 -38
- package/clis/douyin/activities.test.js +0 -55
- package/clis/douyin/collections.test.js +0 -45
- package/clis/douyin/delete.test.js +0 -100
- package/clis/douyin/draft.test.js +0 -335
- package/clis/douyin/drafts.test.js +0 -11
- package/clis/douyin/hashtag.test.js +0 -171
- package/clis/douyin/location.test.js +0 -23
- package/clis/douyin/profile.test.js +0 -46
- package/clis/douyin/publish-upload-id.test.js +0 -170
- package/clis/douyin/publish.test.js +0 -38
- package/clis/douyin/stats.test.js +0 -22
- package/clis/douyin/update.test.js +0 -11
- package/clis/douyin/user-videos.test.js +0 -151
- package/clis/douyin/videos.test.js +0 -54
- package/clis/duckduckgo/search.test.js +0 -128
- package/clis/duckduckgo/suggest.test.js +0 -66
- package/clis/eastmoney/hot-rank.test.js +0 -59
- package/clis/endoflife/endoflife.test.js +0 -51
- package/clis/facebook/feed.test.js +0 -169
- package/clis/facebook/marketplace.test.js +0 -91
- package/clis/facebook/notifications.test.js +0 -458
- package/clis/facebook/search.test.js +0 -55
- package/clis/flathub/flathub.test.js +0 -90
- package/clis/flomo/memos.test.js +0 -144
- package/clis/gemini/ask.test.js +0 -100
- package/clis/gemini/deep-research-result.test.js +0 -155
- package/clis/gemini/deep-research.test.js +0 -181
- package/clis/gemini/reply-state.test.js +0 -671
- package/clis/gemini/utils.test.js +0 -198
- package/clis/geogebra/geogebra.test.js +0 -175
- package/clis/gitee/search.test.js +0 -65
- package/clis/gitee/user.test.js +0 -63
- package/clis/google/utils.test.js +0 -75
- package/clis/google-scholar/cite.test.js +0 -47
- package/clis/google-scholar/profile.test.js +0 -49
- package/clis/google-scholar/search.test.js +0 -71
- package/clis/goproxy/goproxy.test.js +0 -103
- package/clis/gov-law/commands.test.js +0 -39
- package/clis/gov-policy/gov-policy.test.js +0 -224
- package/clis/grok/ask.test.js +0 -29
- package/clis/grok/utils.test.js +0 -103
- package/clis/hackernews/hackernews.test.js +0 -132
- package/clis/hf/hf.test.js +0 -16
- package/clis/hupu/hot.test.js +0 -224
- package/clis/imdb/utils.test.js +0 -88
- package/clis/indeed/indeed.test.js +0 -375
- package/clis/instagram/_shared/private-publish.test.js +0 -705
- package/clis/instagram/_shared/protocol-capture.test.js +0 -114
- package/clis/instagram/download.test.js +0 -118
- package/clis/instagram/note.test.js +0 -53
- package/clis/instagram/post.test.js +0 -1647
- package/clis/instagram/reel.test.js +0 -167
- package/clis/instagram/story.test.js +0 -134
- package/clis/jd/commands.test.js +0 -41
- package/clis/jd/item.test.js +0 -354
- package/clis/jianyu/search.test.js +0 -153
- package/clis/jianyu/shared/procurement-contract.test.js +0 -72
- package/clis/jianyu/shared/procurement-detail.test.js +0 -84
- package/clis/jike/read.test.js +0 -86
- package/clis/jira/commands.test.js +0 -287
- package/clis/juejin/search.js +0 -123
- package/clis/juejin/search.test.js +0 -98
- package/clis/lesswrong/frontpage.test.js +0 -37
- package/clis/lichess/lichess.test.js +0 -85
- package/clis/linkedin/connect.test.js +0 -213
- package/clis/linkedin/inbox.test.js +0 -152
- package/clis/linkedin/job-detail.test.js +0 -38
- package/clis/linkedin/jobs-preferences.test.js +0 -43
- package/clis/linkedin/people-search.test.js +0 -216
- package/clis/linkedin/post-analytics.test.js +0 -40
- package/clis/linkedin/posts.test.js +0 -40
- package/clis/linkedin/profile-analytics.test.js +0 -67
- package/clis/linkedin/profile-experience.test.js +0 -152
- package/clis/linkedin/profile-projects.test.js +0 -111
- package/clis/linkedin/profile-read.test.js +0 -77
- package/clis/linkedin/safe-send.test.js +0 -204
- package/clis/linkedin/salesnav-inbox.test.js +0 -113
- package/clis/linkedin/salesnav-message.test.js +0 -172
- package/clis/linkedin/salesnav-search.test.js +0 -76
- package/clis/linkedin/salesnav-thread.test.js +0 -79
- package/clis/linkedin/search.test.js +0 -222
- package/clis/linkedin/sent-invitations.test.js +0 -62
- package/clis/linkedin/services-read.test.js +0 -105
- package/clis/linkedin/thread-snapshot.test.js +0 -89
- package/clis/linkedin/timeline.test.js +0 -81
- package/clis/linkedin-learning/course.test.js +0 -114
- package/clis/linkedin-learning/search.test.js +0 -144
- package/clis/linkedin-learning/trending.test.js +0 -123
- package/clis/linux-do/feed.test.js +0 -153
- package/clis/linux-do/topic-content.test.js +0 -59
- package/clis/lobsters/lobsters.test.js +0 -169
- package/clis/notebooklm/add-source.test.js +0 -97
- package/clis/notebooklm/compat.test.js +0 -16
- package/clis/notebooklm/create.test.js +0 -58
- package/clis/notebooklm/generate-audio.test.js +0 -63
- package/clis/notebooklm/generate-slides.test.js +0 -75
- package/clis/notebooklm/history.test.js +0 -58
- package/clis/notebooklm/note-list.test.js +0 -56
- package/clis/notebooklm/notes-get.test.js +0 -72
- package/clis/notebooklm/open.test.js +0 -63
- package/clis/notebooklm/rpc.test.js +0 -131
- package/clis/notebooklm/source-fulltext.test.js +0 -106
- package/clis/notebooklm/source-get.test.js +0 -84
- package/clis/notebooklm/source-guide.test.js +0 -104
- package/clis/notebooklm/summary.test.js +0 -78
- package/clis/notebooklm/utils.test.js +0 -449
- package/clis/notebooklm/write-note.test.js +0 -70
- package/clis/nuget/nuget.test.js +0 -111
- package/clis/oeis/oeis.test.js +0 -88
- package/clis/ones/task-helpers.test.js +0 -12
- package/clis/ones/worklog.test.js +0 -20
- package/clis/openfda/openfda.test.js +0 -114
- package/clis/openreview/openreview.test.js +0 -427
- package/clis/osv/osv.test.js +0 -97
- package/clis/paperreview/commands.test.js +0 -243
- package/clis/paperreview/utils.test.js +0 -49
- package/clis/pixiv/download.test.js +0 -87
- package/clis/pixiv/illusts.test.js +0 -99
- package/clis/pixiv/search.test.js +0 -83
- package/clis/powerchina/search.test.js +0 -93
- package/clis/producthunt/utils.test.js +0 -64
- package/clis/pubmed/pubmed.test.js +0 -276
- package/clis/quark/utils.test.js +0 -58
- package/clis/qwen/utils.test.js +0 -45
- package/clis/reddit/extract-media.test.js +0 -149
- package/clis/reddit/frontpage.test.js +0 -34
- package/clis/reddit/home.test.js +0 -170
- package/clis/reddit/hot.test.js +0 -32
- package/clis/reddit/popular.test.js +0 -26
- package/clis/reddit/read.test.js +0 -335
- package/clis/reddit/reply.test.js +0 -89
- package/clis/reddit/search.test.js +0 -26
- package/clis/reddit/subreddit-info.test.js +0 -163
- package/clis/reddit/subreddit.test.js +0 -31
- package/clis/reddit/subscribed.test.js +0 -168
- package/clis/reddit/whoami.test.js +0 -105
- package/clis/rednote/navigation.test.js +0 -26
- package/clis/rednote/rednote.test.js +0 -222
- package/clis/rest-countries/rest-countries.test.js +0 -83
- package/clis/reuters/reuters.test.js +0 -299
- package/clis/rfc/rfc.test.js +0 -74
- package/clis/sinafinance/stock.test.js +0 -59
- package/clis/spotify/utils.test.js +0 -67
- package/clis/stackoverflow/stackoverflow.test.js +0 -404
- package/clis/steam/steam.test.js +0 -46
- package/clis/substack/utils.test.js +0 -44
- package/clis/suno/commands.test.js +0 -188
- package/clis/suno/download.test.js +0 -151
- package/clis/suno/generate.test.js +0 -252
- package/clis/suno/utils.test.js +0 -329
- package/clis/taobao/commands.test.js +0 -41
- package/clis/tdx/hot-rank.test.js +0 -59
- package/clis/ths/hot-rank.test.js +0 -64
- package/clis/tieba/commands.test.js +0 -79
- package/clis/tieba/utils.test.js +0 -290
- package/clis/tiktok/creator-videos.test.js +0 -113
- package/clis/tiktok/refactor.test.js +0 -389
- package/clis/tiktok/write-refactor.test.js +0 -370
- package/clis/toutiao/toutiao.test.js +0 -378
- package/clis/tvmaze/tvmaze.test.js +0 -93
- package/clis/twitter/bookmark-folder.test.js +0 -395
- package/clis/twitter/bookmark-folders.test.js +0 -152
- package/clis/twitter/bookmark.test.js +0 -74
- package/clis/twitter/bookmarks.test.js +0 -205
- package/clis/twitter/delete.test.js +0 -85
- package/clis/twitter/device-follow.test.js +0 -287
- package/clis/twitter/download.test.js +0 -457
- package/clis/twitter/followers.test.js +0 -62
- package/clis/twitter/following.test.js +0 -338
- package/clis/twitter/hide-reply.test.js +0 -76
- package/clis/twitter/like.test.js +0 -73
- package/clis/twitter/likes.test.js +0 -220
- package/clis/twitter/list-add.test.js +0 -133
- package/clis/twitter/list-create.test.js +0 -169
- package/clis/twitter/list-remove.test.js +0 -110
- package/clis/twitter/list-tweets.test.js +0 -196
- package/clis/twitter/lists.test.js +0 -253
- package/clis/twitter/post.test.js +0 -258
- package/clis/twitter/profile.test.js +0 -39
- package/clis/twitter/quote.test.js +0 -194
- package/clis/twitter/reply.test.js +0 -237
- package/clis/twitter/retweet.test.js +0 -73
- package/clis/twitter/search.test.js +0 -349
- package/clis/twitter/shared.test.js +0 -794
- package/clis/twitter/thread.test.js +0 -58
- package/clis/twitter/timeline.test.js +0 -104
- package/clis/twitter/trending.test.js +0 -15
- package/clis/twitter/tweets.test.js +0 -362
- package/clis/twitter/unbookmark.test.js +0 -73
- package/clis/twitter/unlike.test.js +0 -75
- package/clis/twitter/unretweet.test.js +0 -73
- package/clis/twitter/utils.test.js +0 -169
- package/clis/uisdc/news.test.js +0 -66
- package/clis/uiverse/_shared.test.js +0 -62
- package/clis/uiverse/navigation.test.js +0 -12
- package/clis/upwork/upwork.test.js +0 -566
- package/clis/wanfang/search.test.js +0 -23
- package/clis/web/read.test.js +0 -392
- package/clis/weibo/delete.test.js +0 -94
- package/clis/weibo/envelope.test.js +0 -85
- package/clis/weibo/favorites.test.js +0 -114
- package/clis/weibo/publish.test.js +0 -192
- package/clis/weibo/user-posts.test.js +0 -92
- package/clis/weibo/utils.test.js +0 -36
- package/clis/weixin/drafts.test.js +0 -69
- package/clis/weixin/search.test.js +0 -227
- package/clis/weread/ai-outline.test.js +0 -83
- package/clis/weread/book-search.test.js +0 -242
- package/clis/weread/commands.test.js +0 -398
- package/clis/weread/private-api-regression.test.js +0 -369
- package/clis/weread/search-regression.test.js +0 -494
- package/clis/weread/utils.test.js +0 -128
- package/clis/weread-official/commands.test.js +0 -385
- package/clis/weread-official/utils.test.js +0 -242
- package/clis/wikidata/wikidata.test.js +0 -83
- package/clis/wikipedia/trending.test.js +0 -57
- package/clis/wttr/wttr.test.js +0 -84
- package/clis/xianyu/chat.test.js +0 -84
- package/clis/xianyu/im.test.js +0 -253
- package/clis/xianyu/item.test.js +0 -56
- package/clis/xianyu/publish.test.js +0 -220
- package/clis/xianyu/search.test.js +0 -17
- package/clis/xiaoe/xiaoe.test.js +0 -486
- package/clis/xiaohongshu/comments.test.js +0 -258
- package/clis/xiaohongshu/creator-note-detail.test.js +0 -451
- package/clis/xiaohongshu/creator-notes-summary.test.js +0 -94
- package/clis/xiaohongshu/creator-notes.test.js +0 -281
- package/clis/xiaohongshu/creator-stats.test.js +0 -24
- package/clis/xiaohongshu/delete-note.test.js +0 -172
- package/clis/xiaohongshu/download.test.js +0 -316
- package/clis/xiaohongshu/navigation.test.js +0 -34
- package/clis/xiaohongshu/note.test.js +0 -249
- package/clis/xiaohongshu/publish.test.js +0 -608
- package/clis/xiaohongshu/search.test.js +0 -325
- package/clis/xiaohongshu/user-helpers.test.js +0 -160
- package/clis/xiaoyuzhou/auth.test.js +0 -124
- package/clis/xiaoyuzhou/download.test.js +0 -135
- package/clis/xiaoyuzhou/podcast-episodes.test.js +0 -78
- package/clis/xiaoyuzhou/transcript.test.js +0 -195
- package/clis/xiaoyuzhou/utils.test.js +0 -39
- package/clis/xueqiu/comments.test.js +0 -696
- package/clis/xueqiu/danjuan-utils.test.js +0 -41
- package/clis/xueqiu/utils.test.js +0 -26
- package/clis/yahoo/search.test.js +0 -94
- package/clis/youdao/note.test.js +0 -99
- package/clis/youtube/channel.test.js +0 -59
- package/clis/youtube/feed.test.js +0 -131
- package/clis/youtube/transcript-group.test.js +0 -99
- package/clis/youtube/transcript.test.js +0 -296
- package/clis/youtube/utils.test.js +0 -84
- package/clis/yuanbao/ask.test.js +0 -124
- package/clis/yuanbao/new.test.js +0 -30
- package/clis/yuanbao/shared.test.js +0 -80
- package/clis/zhihu/answer-comments.test.js +0 -287
- package/clis/zhihu/answer-detail.test.js +0 -338
- package/clis/zhihu/answer.test.js +0 -54
- package/clis/zhihu/collection.test.js +0 -337
- package/clis/zhihu/collections.test.js +0 -182
- package/clis/zhihu/comment.test.js +0 -50
- package/clis/zhihu/download.test.js +0 -12
- package/clis/zhihu/favorite.test.js +0 -59
- package/clis/zhihu/follow.test.js +0 -42
- package/clis/zhihu/like.test.js +0 -41
- package/clis/zhihu/question.test.js +0 -251
- package/clis/zhihu/recommend.test.js +0 -143
- package/clis/zhihu/search.test.js +0 -198
- package/clis/zhihu/target.test.js +0 -77
- package/clis/zhihu/text.test.js +0 -24
- package/clis/zhihu/write-shared.test.js +0 -176
- package/clis/zlibrary/commands.test.js +0 -65
- package/clis/zsxq/search.test.js +0 -24
- package/clis/zsxq/topic.test.js +0 -30
- package/clis/zsxq/topics.test.js +0 -24
- package/clis/zsxq/utils.test.js +0 -31
- package/dist/src/adapter-shadow.test.js +0 -49
- package/dist/src/adapter-source.test.js +0 -29
- package/dist/src/browser/analyze.test.js +0 -166
- package/dist/src/browser/article-extract.e2e.test.js +0 -105
- package/dist/src/browser/article-extract.test.js +0 -94
- package/dist/src/browser/ax-snapshot.test.js +0 -91
- package/dist/src/browser/base-page.test.js +0 -758
- package/dist/src/browser/cdp-click-fixture.test.js +0 -87
- package/dist/src/browser/cdp.test.js +0 -79
- package/dist/src/browser/compound.test.js +0 -175
- package/dist/src/browser/daemon-client.test.js +0 -199
- package/dist/src/browser/dom-helpers.test.js +0 -106
- package/dist/src/browser/dom-snapshot.test.js +0 -313
- package/dist/src/browser/errors.test.js +0 -60
- package/dist/src/browser/extract.test.js +0 -129
- package/dist/src/browser/find.test.js +0 -180
- package/dist/src/browser/html-tree.test.js +0 -181
- package/dist/src/browser/network-cache.test.js +0 -58
- package/dist/src/browser/network-key.test.js +0 -49
- package/dist/src/browser/page.test.js +0 -378
- package/dist/src/browser/shape-filter.test.js +0 -101
- package/dist/src/browser/shape.test.js +0 -82
- package/dist/src/browser/stealth.test.js +0 -134
- package/dist/src/browser/target-errors.test.js +0 -94
- package/dist/src/browser/target-resolver.test.js +0 -118
- package/dist/src/browser/utils.test.js +0 -29
- package/dist/src/browser/verify-fixture.test.js +0 -219
- package/dist/src/browser.test.js +0 -232
- package/dist/src/build-manifest.test.js +0 -341
- package/dist/src/capabilityRouting.test.js +0 -63
- package/dist/src/cli-argv-preprocess.test.js +0 -209
- package/dist/src/cli.test.js +0 -2803
- package/dist/src/commanderAdapter.test.js +0 -352
- package/dist/src/commands/daemon.test.js +0 -219
- package/dist/src/completion.test.js +0 -24
- package/dist/src/convention-audit.test.js +0 -247
- package/dist/src/daemon.test.js +0 -60
- package/dist/src/doctor.test.js +0 -263
- package/dist/src/download/article-download.test.js +0 -235
- package/dist/src/download/index.test.js +0 -140
- package/dist/src/download/media-download.test.js +0 -112
- package/dist/src/download/progress.test.js +0 -36
- package/dist/src/electron-apps.test.js +0 -49
- package/dist/src/engine.test.js +0 -312
- package/dist/src/errors.test.js +0 -109
- package/dist/src/execution.test.js +0 -623
- package/dist/src/extension-manifest-regression.test.js +0 -12
- package/dist/src/external.test.js +0 -146
- package/dist/src/help.test.js +0 -58
- package/dist/src/hooks.test.js +0 -92
- package/dist/src/interceptor.test.js +0 -81
- package/dist/src/launcher.test.js +0 -138
- package/dist/src/node-network.test.js +0 -55
- package/dist/src/observation/artifact.test.js +0 -121
- package/dist/src/observation/manager.test.js +0 -13
- package/dist/src/observation/redaction.test.js +0 -32
- package/dist/src/observation/retention.test.js +0 -118
- package/dist/src/observation/ring-buffer.test.js +0 -22
- package/dist/src/output.test.js +0 -53
- package/dist/src/package-exports.test.js +0 -112
- package/dist/src/pipeline/executor.test.js +0 -182
- package/dist/src/pipeline/steps/download.test.js +0 -102
- package/dist/src/pipeline/steps/fetch.test.js +0 -117
- package/dist/src/pipeline/template.test.js +0 -175
- package/dist/src/pipeline/transform.test.js +0 -133
- package/dist/src/plugin-manifest.test.js +0 -179
- package/dist/src/plugin-scaffold.test.js +0 -83
- package/dist/src/plugin.test.js +0 -1324
- package/dist/src/registry.test.js +0 -209
- package/dist/src/runtime-detect.test.js +0 -39
- package/dist/src/serialization.test.js +0 -78
- package/dist/src/snapshotFormatter.test.js +0 -521
- package/dist/src/tui.test.js +0 -19
- package/dist/src/update-check.test.js +0 -116
- package/dist/src/utils.test.js +0 -155
- package/dist/src/validate.test.js +0 -90
- package/dist/src/weixin-download.test.js +0 -45
package/clis/reddit/read.test.js
DELETED
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { ArgumentError, AuthRequiredError, CommandExecutionError, EmptyResultError } from '@sovovs/bycli/errors';
|
|
4
|
-
import { normalizeRedditPostId, parseExpandRounds } from './read.js';
|
|
5
|
-
import './read.js';
|
|
6
|
-
|
|
7
|
-
function makePage(result) {
|
|
8
|
-
return {
|
|
9
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
10
|
-
evaluate: vi.fn().mockResolvedValue(result),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function redditPostEnvelope(children) {
|
|
15
|
-
return [
|
|
16
|
-
{
|
|
17
|
-
data: {
|
|
18
|
-
children: [{
|
|
19
|
-
data: {
|
|
20
|
-
title: 'Post title',
|
|
21
|
-
selftext: '',
|
|
22
|
-
author: 'op',
|
|
23
|
-
score: 10,
|
|
24
|
-
is_self: true,
|
|
25
|
-
},
|
|
26
|
-
}],
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
{ data: { children } },
|
|
30
|
-
];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function commentThing(id, body, parent = 't3_abc123', score = 1) {
|
|
34
|
-
return {
|
|
35
|
-
kind: 't1',
|
|
36
|
-
data: {
|
|
37
|
-
id,
|
|
38
|
-
name: `t1_${id}`,
|
|
39
|
-
parent_id: parent,
|
|
40
|
-
author: id,
|
|
41
|
-
score,
|
|
42
|
-
body,
|
|
43
|
-
replies: '',
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function moreThing(id, children, parent = 't3_abc123', count = children.length) {
|
|
49
|
-
return {
|
|
50
|
-
kind: 'more',
|
|
51
|
-
data: { id, parent_id: parent, children, count },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function jsonResponse(payload, status = 200) {
|
|
56
|
-
return {
|
|
57
|
-
ok: status >= 200 && status < 300,
|
|
58
|
-
status,
|
|
59
|
-
json: vi.fn().mockResolvedValue(payload),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function makeRuntimePage(fetchImpl) {
|
|
64
|
-
return {
|
|
65
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
66
|
-
evaluate: vi.fn(async (script) => {
|
|
67
|
-
const previousFetch = globalThis.fetch;
|
|
68
|
-
globalThis.fetch = fetchImpl;
|
|
69
|
-
try {
|
|
70
|
-
return await eval(script);
|
|
71
|
-
} finally {
|
|
72
|
-
globalThis.fetch = previousFetch;
|
|
73
|
-
}
|
|
74
|
-
}),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
describe('reddit read adapter', () => {
|
|
79
|
-
const command = getRegistry().get('reddit/read');
|
|
80
|
-
|
|
81
|
-
it('uses an ephemeral Reddit site tab by default', () => {
|
|
82
|
-
expect(command?.browser).toBe(true);
|
|
83
|
-
expect(command?.siteSession).toBeUndefined();
|
|
84
|
-
expect(command?.columns).toEqual(['type', 'author', 'score', 'text']);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('exposes the new --expand-more / --expand-rounds args', () => {
|
|
88
|
-
const argNames = command.args.map((a) => a.name);
|
|
89
|
-
expect(argNames).toContain('expand-more');
|
|
90
|
-
expect(argNames).toContain('expand-rounds');
|
|
91
|
-
const expandMore = command.args.find((a) => a.name === 'expand-more');
|
|
92
|
-
expect(expandMore.type).toBe('bool');
|
|
93
|
-
expect(expandMore.default).toBe(false);
|
|
94
|
-
const rounds = command.args.find((a) => a.name === 'expand-rounds');
|
|
95
|
-
expect(rounds.type).toBe('int');
|
|
96
|
-
expect(rounds.default).toBe(2);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('normalizeRedditPostId', () => {
|
|
100
|
-
it('accepts bare ids, t3 fullnames, and exact reddit post URLs', () => {
|
|
101
|
-
expect(normalizeRedditPostId('1AbC23')).toBe('1abc23');
|
|
102
|
-
expect(normalizeRedditPostId('t3_1AbC23')).toBe('1abc23');
|
|
103
|
-
expect(normalizeRedditPostId('https://www.reddit.com/r/bycli/comments/1abc23/title_slug/?sort=top')).toBe('1abc23');
|
|
104
|
-
expect(normalizeRedditPostId('https://www.reddit.com/r/bycli/comments/1abc23/title_slug/okf3s7u/?context=3')).toBe('1abc23');
|
|
105
|
-
expect(normalizeRedditPostId('https://old.reddit.com/comments/1abc23/title_slug/')).toBe('1abc23');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('rejects invalid or structurally loose post identities before navigation', () => {
|
|
109
|
-
for (const bad of [
|
|
110
|
-
'',
|
|
111
|
-
't1_okf3s7u',
|
|
112
|
-
'https://reddit.com.evil.com/r/bycli/comments/1abc23/title_slug/',
|
|
113
|
-
'http://www.reddit.com/r/bycli/comments/1abc23/title_slug/',
|
|
114
|
-
'https://www.reddit.com/r/bycli/comments/',
|
|
115
|
-
'https://www.reddit.com/r/bycli/comments/1abc23/title_slug/okf3s7u/evil',
|
|
116
|
-
'not/a/post',
|
|
117
|
-
]) {
|
|
118
|
-
expect(() => normalizeRedditPostId(bad)).toThrow(ArgumentError);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe('parseExpandRounds', () => {
|
|
124
|
-
it('returns the default for absent input but throws on out-of-range / non-integer', () => {
|
|
125
|
-
expect(parseExpandRounds(undefined)).toBe(2);
|
|
126
|
-
expect(parseExpandRounds(null)).toBe(2);
|
|
127
|
-
expect(parseExpandRounds('')).toBe(2);
|
|
128
|
-
expect(parseExpandRounds(1)).toBe(1);
|
|
129
|
-
expect(parseExpandRounds(5)).toBe(5);
|
|
130
|
-
for (const bad of [0, -1, 6, 1.5, NaN, 'abc']) {
|
|
131
|
-
expect(() => parseExpandRounds(bad)).toThrow(ArgumentError);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('rejects a bad --expand-rounds BEFORE navigating', async () => {
|
|
137
|
-
const page = makePage({ kind: 'ok', rows: [] });
|
|
138
|
-
await expect(command.func(page, { 'post-id': 'abc123', 'expand-rounds': 99 }))
|
|
139
|
-
.rejects.toBeInstanceOf(ArgumentError);
|
|
140
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
141
|
-
expect(page.evaluate).not.toHaveBeenCalled();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('rejects a bad post identity BEFORE navigating', async () => {
|
|
145
|
-
const page = makePage({ kind: 'ok', rows: [] });
|
|
146
|
-
await expect(command.func(page, { 'post-id': 'https://evil.test/r/x/comments/abc/title/' }))
|
|
147
|
-
.rejects.toBeInstanceOf(ArgumentError);
|
|
148
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
149
|
-
expect(page.evaluate).not.toHaveBeenCalled();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('returns rows when the evaluate script reports kind=ok', async () => {
|
|
153
|
-
const page = makePage({
|
|
154
|
-
kind: 'ok',
|
|
155
|
-
rows: [
|
|
156
|
-
{ type: 'POST', author: 'alice', score: 10, text: 'Title' },
|
|
157
|
-
{ type: 'L0', author: 'bob', score: 5, text: 'Comment' },
|
|
158
|
-
],
|
|
159
|
-
expandMeta: { rounds: 0, fetched: 0, capped: false, errors: [] },
|
|
160
|
-
});
|
|
161
|
-
const result = await command.func(page, { 'post-id': 'abc123', limit: 5 });
|
|
162
|
-
expect(page.goto).toHaveBeenCalledWith('https://www.reddit.com');
|
|
163
|
-
expect(result).toEqual([
|
|
164
|
-
{ type: 'POST', author: 'alice', score: 10, text: 'Title' },
|
|
165
|
-
{ type: 'L0', author: 'bob', score: 5, text: 'Comment' },
|
|
166
|
-
]);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('maps the five failure kinds to the right typed errors', async () => {
|
|
170
|
-
await expect(command.func(makePage({ kind: 'inaccessible', detail: 'post 403' }), { 'post-id': 'abc123' }))
|
|
171
|
-
.rejects.toBeInstanceOf(EmptyResultError);
|
|
172
|
-
|
|
173
|
-
await expect(command.func(makePage({ kind: 'auth', detail: 'morechildren 401' }), { 'post-id': 'abc123' }))
|
|
174
|
-
.rejects.toBeInstanceOf(AuthRequiredError);
|
|
175
|
-
|
|
176
|
-
await expect(command.func(makePage({ kind: 'http', httpStatus: 503, where: '/comments/abc.json' }), { 'post-id': 'abc123' }))
|
|
177
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
178
|
-
|
|
179
|
-
await expect(command.func(makePage({ kind: 'malformed', detail: 'no comment listing' }), { 'post-id': 'abc123' }))
|
|
180
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
181
|
-
|
|
182
|
-
await expect(command.func(makePage({ kind: 'parser-drift', detail: 'walker drift' }), { 'post-id': 'abc123' }))
|
|
183
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
184
|
-
|
|
185
|
-
await expect(command.func(makePage({ kind: 'expand-failed', detail: 'morechildren errors' }), { 'post-id': 'abc123' }))
|
|
186
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('throws CommandExecutionError on an unknown envelope shape (no kind)', async () => {
|
|
190
|
-
await expect(command.func(makePage({ random: 'stuff' }), { 'post-id': 'abc123' }))
|
|
191
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
192
|
-
await expect(command.func(makePage(null), { 'post-id': 'abc123' }))
|
|
193
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('embeds expandMore=false by default and inlines flags into the evaluate script', async () => {
|
|
197
|
-
const page = makePage({ kind: 'ok', rows: [], expandMeta: { rounds: 0, fetched: 0, capped: false, errors: [] } });
|
|
198
|
-
await command.func(page, { 'post-id': 'xyz', sort: 'top', limit: 3 });
|
|
199
|
-
const script = page.evaluate.mock.calls[0][0];
|
|
200
|
-
expect(script).toContain('var expandMore = false');
|
|
201
|
-
expect(script).toContain('var expandRounds = 2');
|
|
202
|
-
expect(script).toContain('var sort = "top"');
|
|
203
|
-
expect(script).toContain('var limit = 3');
|
|
204
|
-
expect(script).toContain('var postId = "xyz"');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('embeds expandMore=true and the requested expandRounds when --expand-more is on', async () => {
|
|
208
|
-
const page = makePage({ kind: 'ok', rows: [], expandMeta: { rounds: 3, fetched: 12, capped: true, errors: [] } });
|
|
209
|
-
await command.func(page, { 'post-id': 'xyz', 'expand-more': true, 'expand-rounds': 3 });
|
|
210
|
-
const script = page.evaluate.mock.calls[0][0];
|
|
211
|
-
expect(script).toContain('var expandMore = true');
|
|
212
|
-
expect(script).toContain('var expandRounds = 3');
|
|
213
|
-
// The /api/morechildren request body construction must be present in
|
|
214
|
-
// the evaluate script (round-trips the link_id + children CSV).
|
|
215
|
-
expect(script).toContain("'/api/morechildren'");
|
|
216
|
-
expect(script).toContain("'api_type=json'");
|
|
217
|
-
expect(script).toContain("encodeURIComponent(linkFullname)");
|
|
218
|
-
expect(script).toContain("encodeURIComponent(batch.join(','))");
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('normalizes a full reddit URL before building the browser script', async () => {
|
|
222
|
-
const page = makePage({ kind: 'ok', rows: [], expandMeta: { rounds: 0, fetched: 0, capped: false, errors: [] } });
|
|
223
|
-
await command.func(page, { 'post-id': 'https://www.reddit.com/r/python/comments/1abc23/title_slug/' });
|
|
224
|
-
const script = page.evaluate.mock.calls[0][0];
|
|
225
|
-
expect(script).toContain('var postId = "1abc23"');
|
|
226
|
-
expect(script).not.toContain('postIdRaw.match');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('expands morechildren in the original tree position instead of appending to the parent', async () => {
|
|
230
|
-
const fetchMock = vi.fn(async (url) => {
|
|
231
|
-
if (String(url).startsWith('/comments/')) {
|
|
232
|
-
return jsonResponse(redditPostEnvelope([
|
|
233
|
-
commentThing('a', 'A'),
|
|
234
|
-
moreThing('more_top', ['b', 'c']),
|
|
235
|
-
commentThing('d', 'D'),
|
|
236
|
-
]));
|
|
237
|
-
}
|
|
238
|
-
if (String(url) === '/api/morechildren') {
|
|
239
|
-
return jsonResponse({
|
|
240
|
-
json: {
|
|
241
|
-
errors: [],
|
|
242
|
-
data: { things: [commentThing('b', 'B'), commentThing('c', 'C')] },
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
throw new Error(`unexpected URL ${url}`);
|
|
247
|
-
});
|
|
248
|
-
const page = makeRuntimePage(fetchMock);
|
|
249
|
-
|
|
250
|
-
const result = await command.func(page, {
|
|
251
|
-
'post-id': 'abc123',
|
|
252
|
-
'expand-more': true,
|
|
253
|
-
limit: 10,
|
|
254
|
-
replies: 10,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
expect(result.map((row) => row.author)).toEqual(['op', 'a', 'b', 'c', 'd']);
|
|
258
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
259
|
-
'/api/morechildren',
|
|
260
|
-
expect.objectContaining({
|
|
261
|
-
method: 'POST',
|
|
262
|
-
body: expect.stringContaining('link_id=t3_abc123'),
|
|
263
|
-
}),
|
|
264
|
-
);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it('fails expand-more when Reddit returns a child that cannot be placed in the requested tree', async () => {
|
|
268
|
-
const fetchMock = vi.fn(async (url) => {
|
|
269
|
-
if (String(url).startsWith('/comments/')) {
|
|
270
|
-
return jsonResponse(redditPostEnvelope([moreThing('more_top', ['b'])]));
|
|
271
|
-
}
|
|
272
|
-
if (String(url) === '/api/morechildren') {
|
|
273
|
-
return jsonResponse({
|
|
274
|
-
json: {
|
|
275
|
-
errors: [],
|
|
276
|
-
data: { things: [commentThing('b', 'B', 't3_other')] },
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
throw new Error(`unexpected URL ${url}`);
|
|
281
|
-
});
|
|
282
|
-
const page = makeRuntimePage(fetchMock);
|
|
283
|
-
|
|
284
|
-
await expect(command.func(page, {
|
|
285
|
-
'post-id': 'abc123',
|
|
286
|
-
'expand-more': true,
|
|
287
|
-
})).rejects.toBeInstanceOf(CommandExecutionError);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('fails expand-more when Reddit omits a requested child instead of silently dropping the stub', async () => {
|
|
291
|
-
const fetchMock = vi.fn(async (url) => {
|
|
292
|
-
if (String(url).startsWith('/comments/')) {
|
|
293
|
-
return jsonResponse(redditPostEnvelope([moreThing('more_top', ['b', 'c'])]));
|
|
294
|
-
}
|
|
295
|
-
if (String(url) === '/api/morechildren') {
|
|
296
|
-
return jsonResponse({
|
|
297
|
-
json: {
|
|
298
|
-
errors: [],
|
|
299
|
-
data: { things: [commentThing('b', 'B')] },
|
|
300
|
-
},
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
throw new Error(`unexpected URL ${url}`);
|
|
304
|
-
});
|
|
305
|
-
const page = makeRuntimePage(fetchMock);
|
|
306
|
-
|
|
307
|
-
await expect(command.func(page, {
|
|
308
|
-
'post-id': 'abc123',
|
|
309
|
-
'expand-more': true,
|
|
310
|
-
})).rejects.toBeInstanceOf(CommandExecutionError);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('uses 5-kind discriminated union keys that DO NOT collide with declared columns', () => {
|
|
314
|
-
// Read the evaluate template once to assert the intermediate keys we
|
|
315
|
-
// return on the browser side never name any of `type` / `author` /
|
|
316
|
-
// `score` / `text` (the declared columns) — that pattern would
|
|
317
|
-
// trigger the silent-column-drop audit.
|
|
318
|
-
const page = makePage({ kind: 'ok', rows: [], expandMeta: { rounds: 0, fetched: 0, capped: false, errors: [] } });
|
|
319
|
-
return command.func(page, { 'post-id': 'xyz' }).then(() => {
|
|
320
|
-
const script = page.evaluate.mock.calls[0][0];
|
|
321
|
-
// Each return shape uses kind / detail / httpStatus / where /
|
|
322
|
-
// rows / expandMeta. None overlap with the four declared
|
|
323
|
-
// columns. The walker IS allowed to push column-shaped row
|
|
324
|
-
// objects into `rows` — that's the final shape, not an
|
|
325
|
-
// intermediate one.
|
|
326
|
-
expect(script).toContain("kind: 'inaccessible'");
|
|
327
|
-
expect(script).toContain("kind: 'auth'");
|
|
328
|
-
expect(script).toContain("kind: 'http'");
|
|
329
|
-
expect(script).toContain("kind: 'malformed'");
|
|
330
|
-
expect(script).toContain("kind: 'parser-drift'");
|
|
331
|
-
expect(script).toContain("kind: 'expand-failed'");
|
|
332
|
-
expect(script).toContain("kind: 'ok'");
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@sovovs/bycli/errors';
|
|
4
|
-
import { normalizeRedditCommentFullname, requireReplyText } from './reply.js';
|
|
5
|
-
import './reply.js';
|
|
6
|
-
|
|
7
|
-
function makePage(result = { kind: 'ok', detail: 'Reply posted on t1_okf3s7u as t1_reply123' }) {
|
|
8
|
-
return {
|
|
9
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
10
|
-
evaluate: vi.fn().mockResolvedValue(result),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe('reddit reply command', () => {
|
|
15
|
-
const command = getRegistry().get('reddit/reply');
|
|
16
|
-
|
|
17
|
-
it('normalizes bare ids, fullnames, and exact reddit comment URLs', () => {
|
|
18
|
-
expect(normalizeRedditCommentFullname('okf3s7u')).toBe('t1_okf3s7u');
|
|
19
|
-
expect(normalizeRedditCommentFullname('T1_OKF3S7U')).toBe('t1_okf3s7u');
|
|
20
|
-
expect(normalizeRedditCommentFullname('https://www.reddit.com/r/bycli/comments/1abc23/title_slug/okf3s7u/?context=3')).toBe('t1_okf3s7u');
|
|
21
|
-
expect(normalizeRedditCommentFullname('https://old.reddit.com/r/bycli/comments/1abc23/title_slug/okf3s7u/')).toBe('t1_okf3s7u');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('rejects invalid or ambiguous comment identities before navigation', async () => {
|
|
25
|
-
const page = makePage();
|
|
26
|
-
|
|
27
|
-
for (const value of [
|
|
28
|
-
'',
|
|
29
|
-
't3_1abc23',
|
|
30
|
-
'abc/def',
|
|
31
|
-
'https://reddit.com.evil.com/r/bycli/comments/1abc23/title_slug/okf3s7u/',
|
|
32
|
-
'http://www.reddit.com/r/bycli/comments/1abc23/title_slug/okf3s7u/',
|
|
33
|
-
'https://www.reddit.com/r/bycli/comments/1abc23/title_slug/',
|
|
34
|
-
'https://www.reddit.com/r/bycli/comments/1abc23/title_slug/okf3s7u/evil',
|
|
35
|
-
]) {
|
|
36
|
-
await expect(command.func(page, { 'comment-id': value, text: 'hello' })).rejects.toBeInstanceOf(ArgumentError);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
40
|
-
expect(page.evaluate).not.toHaveBeenCalled();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('rejects blank reply text before navigation', async () => {
|
|
44
|
-
const page = makePage();
|
|
45
|
-
|
|
46
|
-
await expect(command.func(page, { 'comment-id': 'okf3s7u', text: ' ' })).rejects.toBeInstanceOf(ArgumentError);
|
|
47
|
-
|
|
48
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
49
|
-
expect(page.evaluate).not.toHaveBeenCalled();
|
|
50
|
-
expect(() => requireReplyText('hello')).not.toThrow();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('posts to the normalized t1 fullname and returns success only on ok result', async () => {
|
|
54
|
-
const page = makePage();
|
|
55
|
-
|
|
56
|
-
const rows = await command.func(page, {
|
|
57
|
-
'comment-id': 'https://www.reddit.com/r/bycli/comments/1abc23/title_slug/okf3s7u/',
|
|
58
|
-
text: 'hello',
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
expect(page.goto).toHaveBeenCalledWith('https://www.reddit.com');
|
|
62
|
-
const script = page.evaluate.mock.calls[0][0];
|
|
63
|
-
expect(script).toContain('const fullname = "t1_okf3s7u"');
|
|
64
|
-
expect(script).toContain('const text = "hello"');
|
|
65
|
-
expect(rows).toEqual([{ status: 'success', message: 'Reply posted on t1_okf3s7u as t1_reply123' }]);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('maps auth, http, reddit, exception, and postcondition failures to typed errors', async () => {
|
|
69
|
-
await expect(command.func(makePage({ kind: 'auth', detail: 'login required' }), { 'comment-id': 'okf3s7u', text: 'hello' }))
|
|
70
|
-
.rejects.toBeInstanceOf(AuthRequiredError);
|
|
71
|
-
await expect(command.func(makePage({ kind: 'http', httpStatus: 500, where: '/api/comment' }), { 'comment-id': 'okf3s7u', text: 'hello' }))
|
|
72
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
73
|
-
await expect(command.func(makePage({ kind: 'reddit-error', detail: 'RATELIMIT: try later' }), { 'comment-id': 'okf3s7u', text: 'hello' }))
|
|
74
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
75
|
-
await expect(command.func(makePage({ kind: 'exception', detail: 'bad json' }), { 'comment-id': 'okf3s7u', text: 'hello' }))
|
|
76
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
77
|
-
await expect(command.func(makePage({ kind: 'postcondition', detail: 'Reddit comment response did not include a created reply id' }), { 'comment-id': 'okf3s7u', text: 'hello' }))
|
|
78
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('requires the Reddit response to include a created reply id', async () => {
|
|
82
|
-
const page = makePage();
|
|
83
|
-
|
|
84
|
-
await command.func(page, { 'comment-id': 'okf3s7u', text: 'hello' });
|
|
85
|
-
|
|
86
|
-
expect(page.evaluate.mock.calls[0][0]).toContain('Reddit comment response did not include a created reply id');
|
|
87
|
-
expect(page.evaluate.mock.calls[0][0]).toContain("String(thing?.data?.name || '').startsWith('t1_')");
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import './search.js';
|
|
4
|
-
|
|
5
|
-
describe('reddit search adapter', () => {
|
|
6
|
-
const command = getRegistry().get('reddit/search');
|
|
7
|
-
|
|
8
|
-
it('exposes the full search-result shape including the 4 media columns', () => {
|
|
9
|
-
expect(command?.columns).toEqual([
|
|
10
|
-
'id', 'title', 'subreddit', 'author', 'score', 'comments', 'url',
|
|
11
|
-
'created_utc', 'selftext',
|
|
12
|
-
'post_hint', 'url_overridden_by_dest', 'preview_image_url', 'gallery_urls',
|
|
13
|
-
]);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('surfaces media via extractRedditMedia in evaluate + map', () => {
|
|
17
|
-
expect(command?.pipeline?.[1]?.evaluate).toContain('function extractRedditMedia');
|
|
18
|
-
expect(command?.pipeline?.[1]?.evaluate).toContain('...extractRedditMedia(c.data)');
|
|
19
|
-
expect(command?.pipeline?.[2]?.map).toMatchObject({
|
|
20
|
-
post_hint: '${{ item.post_hint }}',
|
|
21
|
-
url_overridden_by_dest: '${{ item.url_overridden_by_dest }}',
|
|
22
|
-
preview_image_url: '${{ item.preview_image_url }}',
|
|
23
|
-
gallery_urls: '${{ item.gallery_urls }}',
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@sovovs/bycli/errors';
|
|
4
|
-
import { parseSubredditName } from './subreddit-info.js';
|
|
5
|
-
import './subreddit-info.js';
|
|
6
|
-
|
|
7
|
-
function makePage(result) {
|
|
8
|
-
return {
|
|
9
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
10
|
-
evaluate: vi.fn().mockResolvedValue(result),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe('reddit subreddit-info command', () => {
|
|
15
|
-
const command = getRegistry().get('reddit/subreddit-info');
|
|
16
|
-
|
|
17
|
-
it('registers with the expected shape', () => {
|
|
18
|
-
expect(command).toBeDefined();
|
|
19
|
-
expect(command.access).toBe('read');
|
|
20
|
-
expect(command.browser).toBe(true);
|
|
21
|
-
expect(command.columns).toEqual(['field', 'value']);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('parseSubredditName strips prefixes and validates the name shape', () => {
|
|
25
|
-
expect(parseSubredditName('python')).toBe('python');
|
|
26
|
-
expect(parseSubredditName('r/python')).toBe('python');
|
|
27
|
-
expect(parseSubredditName('/r/python')).toBe('python');
|
|
28
|
-
expect(parseSubredditName(' AskReddit ')).toBe('AskReddit');
|
|
29
|
-
expect(parseSubredditName('aw3some_sub')).toBe('aw3some_sub');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('parseSubredditName rejects invalid names without silent fallback', () => {
|
|
33
|
-
for (const bad of [
|
|
34
|
-
'',
|
|
35
|
-
' ',
|
|
36
|
-
'py', // too short
|
|
37
|
-
'1abc', // must start with letter
|
|
38
|
-
'_sub', // must start with letter
|
|
39
|
-
'has space', // no spaces
|
|
40
|
-
'has-dash', // no dashes
|
|
41
|
-
'way_too_long_subreddit_name_here', // too long (>21)
|
|
42
|
-
]) {
|
|
43
|
-
expect(() => parseSubredditName(bad)).toThrow(ArgumentError);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('rejects bad subreddit names BEFORE navigating', async () => {
|
|
48
|
-
const page = makePage({ kind: 'ok', info: {} });
|
|
49
|
-
await expect(command.func(page, { name: 'has space' })).rejects.toBeInstanceOf(ArgumentError);
|
|
50
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
51
|
-
expect(page.evaluate).not.toHaveBeenCalled();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('throws EmptyResultError for missing / banned / private / quarantined subreddits', async () => {
|
|
55
|
-
for (const detail of ['not found', 'banned', 'private', 'quarantined']) {
|
|
56
|
-
await expect(command.func(makePage({ kind: 'missing', detail }), { name: 'python' }))
|
|
57
|
-
.rejects.toBeInstanceOf(EmptyResultError);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('treats HTTP 401/403/404 about.json responses as inaccessible subreddit, not auth-required', async () => {
|
|
62
|
-
for (const status of [401, 403, 404]) {
|
|
63
|
-
const scriptResults = [];
|
|
64
|
-
const page = {
|
|
65
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
66
|
-
evaluate: vi.fn(async (script) => {
|
|
67
|
-
const result = await (new Function('fetch', `return (${script})`))(vi.fn(async () => ({
|
|
68
|
-
status,
|
|
69
|
-
ok: false,
|
|
70
|
-
})));
|
|
71
|
-
scriptResults.push(result);
|
|
72
|
-
return result;
|
|
73
|
-
}),
|
|
74
|
-
};
|
|
75
|
-
await expect(command.func(page, { name: 'python' })).rejects.toBeInstanceOf(EmptyResultError);
|
|
76
|
-
expect(scriptResults[0]).toMatchObject({ kind: 'missing' });
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('throws CommandExecutionError on HTTP and exception failure modes', async () => {
|
|
81
|
-
await expect(command.func(makePage({ kind: 'http', httpStatus: 500, where: 'about.json' }), { name: 'python' }))
|
|
82
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
83
|
-
await expect(command.func(makePage({ kind: 'exception', detail: 'bad' }), { name: 'python' }))
|
|
84
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('throws CommandExecutionError for malformed 200 subreddit payloads', async () => {
|
|
88
|
-
await expect(command.func(makePage({
|
|
89
|
-
kind: 'malformed',
|
|
90
|
-
detail: 'Reddit returned malformed subreddit info for r/python (missing data.display_name).',
|
|
91
|
-
}), { name: 'python' }))
|
|
92
|
-
.rejects.toMatchObject({
|
|
93
|
-
code: 'COMMAND_EXEC',
|
|
94
|
-
message: expect.stringContaining('malformed subreddit info'),
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('maps a normal subreddit payload into typed field/value rows', async () => {
|
|
99
|
-
const info = {
|
|
100
|
-
display_name: 'python',
|
|
101
|
-
display_name_prefixed: 'r/python',
|
|
102
|
-
title: 'Python',
|
|
103
|
-
public_description: ' News about the programming language Python. ',
|
|
104
|
-
subscribers: 1234567,
|
|
105
|
-
active_user_count: 4321,
|
|
106
|
-
over18: false,
|
|
107
|
-
subreddit_type: 'public',
|
|
108
|
-
created_utc: 1201881600, // 2008-02-01
|
|
109
|
-
url: '/r/python/',
|
|
110
|
-
};
|
|
111
|
-
const rows = await command.func(makePage({ kind: 'ok', info }), { name: 'python' });
|
|
112
|
-
const byField = Object.fromEntries(rows.map((r) => [r.field, r.value]));
|
|
113
|
-
|
|
114
|
-
expect(byField.Name).toBe('r/python');
|
|
115
|
-
expect(byField.Title).toBe('Python');
|
|
116
|
-
expect(byField.Subscribers).toBe('1234567');
|
|
117
|
-
expect(byField['Active Now']).toBe('4321');
|
|
118
|
-
expect(byField.NSFW).toBe('No');
|
|
119
|
-
expect(byField.Type).toBe('public');
|
|
120
|
-
expect(byField.Description).toBe('News about the programming language Python.');
|
|
121
|
-
expect(byField.Created).toBe('2008-02-01');
|
|
122
|
-
expect(byField.URL).toBe('https://www.reddit.com/r/python/');
|
|
123
|
-
|
|
124
|
-
for (const row of rows) {
|
|
125
|
-
expect(Object.keys(row).sort()).toEqual(['field', 'value']);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('falls back to accounts_active when active_user_count is missing', async () => {
|
|
130
|
-
const info = {
|
|
131
|
-
display_name: 'sub',
|
|
132
|
-
display_name_prefixed: 'r/sub',
|
|
133
|
-
accounts_active: 99,
|
|
134
|
-
url: '/r/sub/',
|
|
135
|
-
};
|
|
136
|
-
const rows = await command.func(makePage({ kind: 'ok', info }), { name: 'sub' });
|
|
137
|
-
const byField = Object.fromEntries(rows.map((r) => [r.field, r.value]));
|
|
138
|
-
expect(byField['Active Now']).toBe('99');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('emits typed null (not "-" sentinel) when subscribers / activity / created are missing', async () => {
|
|
142
|
-
const info = {
|
|
143
|
-
display_name: 'sparse',
|
|
144
|
-
display_name_prefixed: 'r/sparse',
|
|
145
|
-
url: '/r/sparse/',
|
|
146
|
-
};
|
|
147
|
-
const rows = await command.func(makePage({ kind: 'ok', info }), { name: 'sparse' });
|
|
148
|
-
const byField = Object.fromEntries(rows.map((r) => [r.field, r.value]));
|
|
149
|
-
expect(byField.Subscribers).toBeNull();
|
|
150
|
-
expect(byField['Active Now']).toBeNull();
|
|
151
|
-
expect(byField.Created).toBeNull();
|
|
152
|
-
expect(byField.Description).toBeNull();
|
|
153
|
-
expect(byField.Title).toBeNull();
|
|
154
|
-
expect(byField.Type).toBeNull();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('encodes the subreddit name into the fetch URL embedded in evaluate', async () => {
|
|
158
|
-
const page = makePage({ kind: 'ok', info: { display_name: 'python', display_name_prefixed: 'r/python', url: '/r/python/' } });
|
|
159
|
-
await command.func(page, { name: 'r/python' });
|
|
160
|
-
const script = page.evaluate.mock.calls[0][0];
|
|
161
|
-
expect(script).toContain('const sub = "python"');
|
|
162
|
-
});
|
|
163
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import './subreddit.js';
|
|
4
|
-
|
|
5
|
-
describe('reddit subreddit adapter', () => {
|
|
6
|
-
const command = getRegistry().get('reddit/subreddit');
|
|
7
|
-
|
|
8
|
-
it('exposes the full subreddit-list shape including the 4 media columns', () => {
|
|
9
|
-
expect(command?.columns).toEqual([
|
|
10
|
-
'id', 'title', 'subreddit', 'author', 'upvotes', 'comments', 'url',
|
|
11
|
-
'created_utc', 'selftext',
|
|
12
|
-
'post_hint', 'url_overridden_by_dest', 'preview_image_url', 'gallery_urls',
|
|
13
|
-
]);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('shapes children into the intermediate-object pattern with media spread in', () => {
|
|
17
|
-
expect(command?.pipeline?.[1]?.evaluate).toContain('function extractRedditMedia');
|
|
18
|
-
expect(command?.pipeline?.[1]?.evaluate).toContain('...extractRedditMedia(c.data)');
|
|
19
|
-
expect(command?.pipeline?.[2]?.map).toMatchObject({
|
|
20
|
-
title: '${{ item.title }}',
|
|
21
|
-
author: '${{ item.author }}',
|
|
22
|
-
upvotes: '${{ item.upvotes }}',
|
|
23
|
-
comments: '${{ item.comments }}',
|
|
24
|
-
url: '${{ item.url }}',
|
|
25
|
-
post_hint: '${{ item.post_hint }}',
|
|
26
|
-
url_overridden_by_dest: '${{ item.url_overridden_by_dest }}',
|
|
27
|
-
preview_image_url: '${{ item.preview_image_url }}',
|
|
28
|
-
gallery_urls: '${{ item.gallery_urls }}',
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
});
|