mirror of
https://github.com/nianzhibai/91.git
synced 2026-06-15 00:44:30 +08:00
7e5e67697e
Implement a new GuangYaPan cloud drive integration across the backend, admin UI, playback proxy, and Spider91 migration flow. Backend changes:\n- Add a GuangYaPan drive driver with token refresh, QR/device login support, directory listing, stream link resolution, directory creation, rename/delete operations, OSS multipart upload, and upload task polling.\n- Register GuangYaPan as a supported storage kind in configuration, catalog normalization, admin APIs, public drive labels, and 302 playback redirects.\n- Allow Spider91 crawler uploads to target GuangYaPan through a dedicated migration adapter.\n- Add scan, thumbnail, preview, and fingerprint cooldown handling for GuangYaPan based on explicit HTTP status codes, Retry-After values, and structured provider codes instead of natural-language message matching.\n- Tighten existing provider cooldown detectors so OneDrive, Google Drive, 115, PikPak, 123pan, Wopan, and media workers avoid treating arbitrary response text as a rate-limit signal.\n- Keep large videos eligible for preview generation unless the user disables preview generation. Admin and tooling changes:\n- Add GuangYaPan as a selectable drive type with QR login UI and token/root-path credential fields.\n- Add crawler upload target support for GuangYaPan in the admin UI.\n- Add drive branding, labels, metadata display, and docs/config examples for GuangYaPan.\n- Include a standalone GuangYaPan QR login helper script for manual credential acquisition. Tests:\n- Add GuangYaPan driver, QR login, proxy, admin API, crawler upload target, fingerprint, cooldown, and form coverage.\n- Update rate-limit tests to assert that message-only throttling text no longer starts cooldowns.\n- Cover explicit HTTP status parsing through shared drive helper tests.
406 lines
18 KiB
TypeScript
406 lines
18 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
const drivesPageSource = readFileSync(
|
|
new URL("../src/admin/DrivesPage.tsx", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const driveComponentsSource = readFileSync(
|
|
new URL("../src/admin/drive/DriveComponents.tsx", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const crawlerPageSource = readFileSync(
|
|
new URL("../src/admin/CrawlersPage.tsx", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const adminLayoutSource = readFileSync(
|
|
new URL("../src/admin/AdminLayout.tsx", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const appSource = readFileSync(
|
|
new URL("../src/App.tsx", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const spider91UploadTargetSource = readFileSync(
|
|
new URL("../src/admin/drive/Spider91UploadTargetField.tsx", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const driveFormSource = readFileSync(
|
|
new URL("../src/admin/drive/DriveForm.tsx", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const adminCss = readFileSync(
|
|
new URL("../src/styles/admin.css", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const apiSource = readFileSync(
|
|
new URL("../src/admin/api.ts", import.meta.url),
|
|
"utf8"
|
|
);
|
|
const constantsSource = readFileSync(
|
|
new URL("../src/admin/drive/constants.ts", import.meta.url),
|
|
"utf8"
|
|
);
|
|
|
|
const combinedSource = drivesPageSource + "\n" + driveFormSource + "\n" + constantsSource + "\n" + spider91UploadTargetSource;
|
|
|
|
function driveTypeOptions() {
|
|
const match = /const DRIVE_OPTIONS:\s*DriveOption\[]\s*=\s*\[([\s\S]*?)\];/.exec(
|
|
driveFormSource
|
|
);
|
|
assert.ok(match, "drive option card list should be present");
|
|
return Array.from(
|
|
match[1].matchAll(/\{\s*kind:\s*"([^"]+)",\s*label:\s*"([^"]+)"/g),
|
|
(option) => ({ value: option[1], label: option[2] })
|
|
);
|
|
}
|
|
|
|
function assertDriveTypeOption(value: string, label: string) {
|
|
assert.ok(
|
|
driveTypeOptions().some((option) => option.value === value && option.label === label),
|
|
`${value} drive type option should be present`
|
|
);
|
|
}
|
|
|
|
test("crawler sources are not selectable as storage drives", () => {
|
|
assert.ok(
|
|
!driveTypeOptions().some((option) => option.value === "spider91"),
|
|
"spider91 should not be a storage drive option"
|
|
);
|
|
assert.ok(
|
|
!driveTypeOptions().some((option) => option.value === "scriptcrawler"),
|
|
"scriptcrawler should not be a storage drive option"
|
|
);
|
|
});
|
|
|
|
test("spider91 upload target uses explicit local-save option instead of auto target", () => {
|
|
assert.match(combinedSource, /本地保存,不上传/);
|
|
assert.match(
|
|
combinedSource,
|
|
/d\.kind === "pikpak"[\s\S]*d\.kind === "p115"[\s\S]*d\.kind === "p123"[\s\S]*d\.kind === "onedrive"[\s\S]*d\.kind === "googledrive"[\s\S]*d\.kind === "wopan"[\s\S]*d\.kind === "guangyapan"/
|
|
);
|
|
assert.match(crawlerPageSource, /UPLOAD_TARGET_KINDS[\s\S]*"wopan"[\s\S]*"guangyapan"/);
|
|
assert.doesNotMatch(combinedSource, /自动:唯一/);
|
|
assert.doesNotMatch(combinedSource, /自动模式/);
|
|
assert.doesNotMatch(combinedSource, /较早的视频会上传到该云盘根目录下的 91 Spider 文件夹/);
|
|
});
|
|
|
|
test("spider91 upload target select uses an aligned custom arrow", () => {
|
|
assert.match(spider91UploadTargetSource, /className="admin-form-select-wrap"/);
|
|
assert.match(spider91UploadTargetSource, /className="admin-form-select"/);
|
|
assert.match(spider91UploadTargetSource, /className="admin-form-select__icon"/);
|
|
assert.match(adminCss, /\.admin-form__row \.admin-form-select\s*\{[^}]*appearance\s*:\s*none/s);
|
|
assert.match(
|
|
adminCss,
|
|
/\.admin-form-select__icon\s*\{[^}]*top\s*:\s*50%[^}]*right\s*:\s*12px[^}]*transform\s*:\s*translateY\(-50%\)/s
|
|
);
|
|
});
|
|
|
|
test("drive form hides root directory id for localstorage and spider91", () => {
|
|
assert.match(combinedSource, /<label[^>]*>根目录 ID<\/label>/);
|
|
assert.match(
|
|
combinedSource,
|
|
/usesRootDirectoryID\(kind:\s*Kind\):\s*boolean\s*\{\s*return kind !== "localstorage" && kind !== "spider91";\s*\}/
|
|
);
|
|
assert.match(combinedSource, /\{usesRootDirectoryID\(form\.kind\) && \(/);
|
|
assert.match(combinedSource, /\{usesRootDirectoryID\(d\.kind\) && \(/);
|
|
assert.match(combinedSource, /placeholder=\{rootIdPlaceholder\(form\.kind\)\}/);
|
|
assert.doesNotMatch(combinedSource, /扫描起点目录 ID/);
|
|
assert.doesNotMatch(combinedSource, /set\("scanRootId"/);
|
|
});
|
|
|
|
test("onedrive drive form only exposes required default-app fields", () => {
|
|
const match =
|
|
/case "onedrive":\s*return \[([\s\S]*?)\];\s*case "googledrive":/.exec(
|
|
combinedSource
|
|
);
|
|
assert.ok(match, "onedrive credential field block should be present");
|
|
const fields = match[1];
|
|
|
|
assert.match(fields, /key: "refresh_token"/);
|
|
assert.doesNotMatch(fields, /key: "access_token"/);
|
|
assert.doesNotMatch(fields, /key: "api_url_address"/);
|
|
assert.doesNotMatch(fields, /key: "region"/);
|
|
assert.doesNotMatch(fields, /key: "is_sharepoint"/);
|
|
assert.doesNotMatch(fields, /key: "site_id"/);
|
|
});
|
|
|
|
test("googledrive drive form supports online API and custom OAuth client modes", () => {
|
|
assertDriveTypeOption("googledrive", "Google Drive");
|
|
|
|
const match =
|
|
/case "googledrive":\s*return \[([\s\S]*?)\];\s*case "localstorage":/.exec(
|
|
combinedSource
|
|
);
|
|
assert.ok(match, "googledrive credential field block should be present");
|
|
const fields = match[1];
|
|
|
|
assert.match(fields, /key: "refresh_token"/);
|
|
assert.match(fields, /key: "use_online_api"/);
|
|
assert.match(fields, /type: "select"/);
|
|
assert.match(fields, /defaultValue: "true"/);
|
|
assert.match(fields, /OpenList 在线 API/);
|
|
assert.match(fields, /自建 Google OAuth 客户端/);
|
|
assert.match(fields, /key: "client_id"/);
|
|
assert.match(fields, /key: "client_secret"/);
|
|
assert.match(fields, /googleDriveUsesOnlineAPI\(creds\)/);
|
|
assert.doesNotMatch(fields, /key: "api_url_address"/);
|
|
assert.doesNotMatch(fields, /在线 API 模式填写 OpenList 获取的 refresh_token/);
|
|
assert.doesNotMatch(constantsSource, /请参考OpenList文档中关于谷歌云盘的配置方法。/);
|
|
assert.doesNotMatch(constantsSource, /选择自建 Google OAuth 客户端后,服务端会直接请求 Google OAuth token 接口续期。/);
|
|
assert.match(driveFormSource, /<select/);
|
|
assert.match(driveFormSource, /value=\{form\.creds\[f\.key\] \?\? f\.defaultValue \?\? ""\}/);
|
|
assert.match(driveFormSource, /className="admin-form-select"/);
|
|
assert.match(driveFormSource, /ChevronDown/);
|
|
assert.match(drivesPageSource, /googleDriveUseOnlineAPI/);
|
|
assert.match(apiSource, /googleDriveUseOnlineAPI\?: boolean/);
|
|
assert.doesNotMatch(fields, /key: "access_token"/);
|
|
});
|
|
|
|
test("pikpak drive form only exposes account login fields", () => {
|
|
const match =
|
|
/case "pikpak":\s*return \[([\s\S]*?)\];\s*case "wopan":/.exec(
|
|
combinedSource
|
|
);
|
|
assert.ok(match, "pikpak credential field block should be present");
|
|
const fields = match[1];
|
|
|
|
assert.match(fields, /key: "username"/);
|
|
assert.match(fields, /key: "password"/);
|
|
assert.doesNotMatch(fields, /key: "platform"/);
|
|
assert.doesNotMatch(fields, /key: "refresh_token"/);
|
|
assert.doesNotMatch(fields, /key: "captcha_token"/);
|
|
assert.doesNotMatch(fields, /key: "device_id"/);
|
|
assert.doesNotMatch(fields, /key: "disable_media_link"/);
|
|
});
|
|
|
|
test("guangyapan drive form exposes qr login and token fields", () => {
|
|
assertDriveTypeOption("guangyapan", "光鸭网盘");
|
|
assert.match(driveFormSource, /GuangYaPanQRCodeLogin/);
|
|
assert.match(driveFormSource, /form\.kind === "guangyapan"/);
|
|
assert.match(apiSource, /startGuangYaPanQRLogin/);
|
|
assert.match(apiSource, /getGuangYaPanQRStatus/);
|
|
|
|
const match =
|
|
/case "guangyapan":\s*return \[([\s\S]*?)\];\s*case "onedrive":/.exec(
|
|
combinedSource
|
|
);
|
|
assert.ok(match, "guangyapan credential field block should be present");
|
|
const fields = match[1];
|
|
|
|
assert.match(fields, /key: "root_path"/);
|
|
assert.match(fields, /key: "refresh_token"/);
|
|
assert.match(fields, /key: "access_token"/);
|
|
assert.doesNotMatch(fields, /key: "phone_number"/);
|
|
assert.doesNotMatch(fields, /key: "send_code"/);
|
|
assert.doesNotMatch(fields, /key: "verify_code"/);
|
|
assert.doesNotMatch(fields, /key: "captcha_token"/);
|
|
assert.doesNotMatch(fields, /key: "client_id"/);
|
|
assert.doesNotMatch(fields, /key: "device_id"/);
|
|
assert.match(combinedSource, /if \(kind === "guangyapan"\) return ""/);
|
|
});
|
|
|
|
test("localstorage drive form asks for a server directory path", () => {
|
|
assertDriveTypeOption("localstorage", "本地存储");
|
|
|
|
const match =
|
|
/case "localstorage":\s*return \[([\s\S]*?)\];\s*case "spider91":/.exec(
|
|
combinedSource
|
|
);
|
|
assert.ok(match, "localstorage credential field block should be present");
|
|
const fields = match[1];
|
|
|
|
assert.match(fields, /key: "path"/);
|
|
assert.match(fields, /label: "本地目录路径"/);
|
|
assert.match(combinedSource, /if \(kind === "localstorage"\) return "\/"/);
|
|
assert.match(combinedSource, /kind !== "localstorage" && kind !== "spider91"/);
|
|
});
|
|
|
|
test("drive type selector keeps primary source order", () => {
|
|
assert.deepEqual(driveTypeOptions(), [
|
|
{ value: "p115", label: "115 网盘" },
|
|
{ value: "p123", label: "123网盘" },
|
|
{ value: "pikpak", label: "PikPak" },
|
|
{ value: "guangyapan", label: "光鸭网盘" },
|
|
{ value: "onedrive", label: "OneDrive" },
|
|
{ value: "googledrive", label: "Google Drive" },
|
|
{ value: "localstorage", label: "本地存储" },
|
|
{ value: "quark", label: "夸克网盘" },
|
|
{ value: "wopan", label: "联通网盘" },
|
|
]);
|
|
});
|
|
|
|
test("crawler management is a separate admin section", () => {
|
|
assert.match(adminLayoutSource, /to="\/admin\/crawlers"/);
|
|
assert.match(adminLayoutSource, /admin-nav__title">爬虫管理/);
|
|
assert.match(adminLayoutSource, /admin-nav__icon"><SpiderIcon size=\{16\} \/>/);
|
|
assert.match(appSource, /path="crawlers" element=\{<CrawlersPage \/>/);
|
|
assert.match(crawlerPageSource, /export function CrawlersPage/);
|
|
assert.match(crawlerPageSource, /SpiderIcon/);
|
|
assert.match(crawlerPageSource, /添加爬虫/);
|
|
// 新设计:列表 + Modal 三步编辑器,删除确认走 ConfirmModal,任务进行中自动轮询
|
|
assert.match(crawlerPageSource, /CrawlerEditorModal/);
|
|
assert.match(crawlerPageSource, /ConfirmModal/);
|
|
assert.doesNotMatch(crawlerPageSource, /window\.confirm/);
|
|
assert.match(crawlerPageSource, /POLL_INTERVAL_MS/);
|
|
assert.match(crawlerPageSource, /api\.listCrawlers/);
|
|
assert.match(crawlerPageSource, /api\.listDrives/);
|
|
assert.match(crawlerPageSource, /api\.upsertCrawler/);
|
|
assert.match(crawlerPageSource, /api\.runCrawler/);
|
|
assert.match(crawlerPageSource, /api\.stopCrawlerTasks/);
|
|
assert.match(crawlerPageSource, /api\.deleteCrawler/);
|
|
assert.match(crawlerPageSource, /api\.importCrawlerScriptFile/);
|
|
assert.match(crawlerPageSource, /api\.importCrawlerScriptURL/);
|
|
assert.match(crawlerPageSource, /api\.testCrawlerScript/);
|
|
assert.match(crawlerPageSource, /type="file"/);
|
|
assert.match(crawlerPageSource, /链接导入/);
|
|
assert.match(crawlerPageSource, /测试脚本/);
|
|
assert.match(crawlerPageSource, /测试通过/);
|
|
assert.match(crawlerPageSource, /Spider91UploadTargetField/);
|
|
assert.match(crawlerPageSource, /uploadDriveId/);
|
|
assert.match(crawlerPageSource, /UPLOAD_TARGET_KINDS/);
|
|
assert.doesNotMatch(crawlerPageSource, /新建脚本/);
|
|
assert.doesNotMatch(crawlerPageSource, /爬虫 ID/);
|
|
assert.doesNotMatch(crawlerPageSource, /crawler-id/);
|
|
assert.doesNotMatch(crawlerPageSource, /crawler-name/);
|
|
// 脚本路径只读展示,不允许手动填写
|
|
assert.doesNotMatch(crawlerPageSource, /crawler-script-path/);
|
|
assert.doesNotMatch(crawlerPageSource, /Python 解释器/);
|
|
assert.doesNotMatch(crawlerPageSource, /自定义配置 JSON/);
|
|
assert.doesNotMatch(crawlerPageSource, /Bot/);
|
|
// 项目不再内置任何爬虫:不允许出现内置 91 预设
|
|
assert.doesNotMatch(crawlerPageSource, /builtin/);
|
|
assert.doesNotMatch(crawlerPageSource, /内置 91/);
|
|
assert.match(apiSource, /type AdminCrawler/);
|
|
assert.match(apiSource, /uploadDriveId\?: string/);
|
|
assert.match(apiSource, /"\/crawlers"/);
|
|
assert.match(apiSource, /"\/crawlers\/import-file"/);
|
|
assert.match(apiSource, /"\/crawlers\/import-url"/);
|
|
assert.match(apiSource, /"\/crawlers\/test-script"/);
|
|
assert.match(apiSource, /type CrawlerDryRunResult/);
|
|
assert.match(apiSource, /id\?: string/);
|
|
assert.match(apiSource, /new FormData\(\)/);
|
|
assert.doesNotMatch(driveFormSource, /scriptcrawler/);
|
|
});
|
|
|
|
test("drive cards use configured abbreviations and visible fallback icon colors", () => {
|
|
assert.match(constantsSource, /googledrive:\s*"GD"/);
|
|
assert.match(constantsSource, /function driveKindAbbr\(kind: string\)/);
|
|
assert.match(constantsSource, /\.slice\(0, 2\)\.toUpperCase\(\)/);
|
|
assert.match(drivesPageSource, /driveKindAbbr\(d\.kind\)/);
|
|
assert.match(adminCss, /\.admin-drive-card__brand-icon\s*\{[^}]*background:\s*var\(--accent\);/s);
|
|
assert.match(adminCss, /\.admin-drive-card__brand-icon\[data-kind="googledrive"\]\s*\{\s*background:\s*#4285f4;\s*\}/);
|
|
assert.match(adminCss, /\.admin-drive-card__brand-icon\[data-kind="guangyapan"\]\s*\{\s*background:\s*var\(--drive-guangyapan\);/);
|
|
});
|
|
|
|
test("drive management exposes stop task controls", () => {
|
|
assert.match(apiSource, /stopDriveTasks/);
|
|
assert.match(apiSource, /\/drives\/\$\{encodeURIComponent\(id\)\}\/tasks\/stop/);
|
|
assert.match(apiSource, /stopAllTasks/);
|
|
assert.match(apiSource, /"\/tasks\/stop"/);
|
|
assert.match(drivesPageSource, /is-stop/);
|
|
assert.match(drivesPageSource, /停止所有任务/);
|
|
assert.match(drivesPageSource, /停止所有网盘任务/);
|
|
});
|
|
|
|
test("drive rescan reports busy storage tasks instead of queueing duplicates", () => {
|
|
assert.match(apiSource, /accepted:\s*boolean;\s*message\?:\s*string/);
|
|
assert.match(apiSource, /scanGenerationStatus\?: DriveGenerationStatus/);
|
|
assert.match(drivesPageSource, /当前存储有正在进行的任务,请稍后重试/);
|
|
assert.match(drivesPageSource, /function isDriveBusy\(d: api\.AdminDrive\)/);
|
|
assert.match(drivesPageSource, /d\.scanGenerationStatus/);
|
|
assert.match(drivesPageSource, /status\?\.state \|\| "idle"/);
|
|
assert.match(drivesPageSource, /scanningDriveIdsRef\.current\.has\(d\.id\)/);
|
|
assert.match(drivesPageSource, /if \(!resp\.accepted\)/);
|
|
assert.doesNotMatch(drivesPageSource, /disabled=\{!!scanningDriveId\}/);
|
|
});
|
|
|
|
test("nightly scan duplicate trigger uses full-scan busy message", () => {
|
|
assert.match(apiSource, /status:\s*NightlyJobStatus;\s*message\?:\s*string/);
|
|
assert.match(drivesPageSource, /当前有全量扫描任务正在进行,请稍后重试/);
|
|
assert.match(drivesPageSource, /resp\.message \|\| NIGHTLY_BUSY_MESSAGE/);
|
|
assert.match(constantsSource, /当前有全量扫描任务正在进行,请稍后重试/);
|
|
});
|
|
|
|
test("drive generation panel shows scan or crawler status first", () => {
|
|
assert.match(driveComponentsSource, /label=\{d\.kind === "spider91" \? "已废弃" : "扫盘"\}/);
|
|
assert.match(driveComponentsSource, /status=\{d\.scanGenerationStatus\}/);
|
|
assert.match(driveComponentsSource, /showCounts=\{false\}/);
|
|
assert.match(driveComponentsSource, /status\?\.scannedCount/);
|
|
assert.match(driveComponentsSource, /预计新增/);
|
|
assert.match(apiSource, /scannedCount:\s*number/);
|
|
assert.match(apiSource, /addedCount:\s*number/);
|
|
assert.match(constantsSource, /if \(state === "scanning"\) return "扫盘中"/);
|
|
});
|
|
|
|
test("legacy spider91 storage is disabled in drive management", () => {
|
|
assert.match(drivesPageSource, /91Spider 不再支持通过网盘运行,请到爬虫管理添加爬虫脚本/);
|
|
assert.match(drivesPageSource, /disabled=\{d\.kind === "spider91"\}/);
|
|
assert.match(drivesPageSource, /已废弃,请到爬虫管理添加/);
|
|
assert.match(constantsSource, /91Spider 不再支持通过网盘添加或编辑/);
|
|
});
|
|
|
|
test("drive detail selection is stored in the URL history", () => {
|
|
assert.match(drivesPageSource, /useSearchParams/);
|
|
assert.match(drivesPageSource, /searchParams\.get\("drive"\)/);
|
|
assert.match(drivesPageSource, /function openDriveDetail\(id: string\)/);
|
|
assert.match(drivesPageSource, /next\.set\("drive", id\)/);
|
|
assert.match(drivesPageSource, /function closeDriveDetail/);
|
|
assert.match(drivesPageSource, /next\.delete\("drive"\)/);
|
|
assert.doesNotMatch(drivesPageSource, /setSelectedDriveId/);
|
|
});
|
|
|
|
test("drive discard confirmation matches delete confirmation modal styling", () => {
|
|
const discardModals = Array.from(
|
|
drivesPageSource.matchAll(/<ConfirmModal[\s\S]*?title="放弃未保存更改"[\s\S]*?\/>/g),
|
|
(match) => match[0]
|
|
);
|
|
|
|
assert.equal(discardModals.length, 2);
|
|
for (const modal of discardModals) {
|
|
assert.match(modal, /danger/);
|
|
assert.match(modal, /centerMessage/);
|
|
assert.match(modal, /modalClassName="admin-modal--delete-confirm"/);
|
|
}
|
|
});
|
|
|
|
test("new drive type selection alone is not treated as unsaved config", () => {
|
|
assert.match(
|
|
drivesPageSource,
|
|
/const formDirty = form\.id\s*\?\s*!sameForm\(form, initialForm\)\s*:\s*hasCreateFormChanges\(form, initialForm\);/
|
|
);
|
|
assert.match(drivesPageSource, /function handleCreateFormChange\(nextForm: FormState\)/);
|
|
assert.match(
|
|
drivesPageSource,
|
|
/if \(!nextForm\.id && !hasCreateFormChanges\(nextForm, initialForm\)\) \{\s*setInitialForm\(nextForm\);/
|
|
);
|
|
assert.match(drivesPageSource, /onChange=\{handleCreateFormChange\}/);
|
|
|
|
const match = /function hasCreateFormChanges\(form: FormState, initial: FormState\): boolean \{([\s\S]*?)\n\}/.exec(
|
|
drivesPageSource
|
|
);
|
|
assert.ok(match, "create form dirty helper should be present");
|
|
const helper = match[1];
|
|
|
|
assert.match(helper, /form\.name\.trim\(\) !== ""/);
|
|
assert.match(helper, /form\.rootId\.trim\(\) !== ""/);
|
|
assert.match(helper, /form\.spider91UploadDriveId !== initial\.spider91UploadDriveId/);
|
|
assert.match(helper, /Object\.values\(form\.creds\)\.some/);
|
|
assert.doesNotMatch(helper, /form\.kind/);
|
|
});
|
|
|
|
test("drive generation actions can resume pending work after stop", () => {
|
|
assert.match(driveComponentsSource, /thumbnailPendingCount/);
|
|
assert.match(driveComponentsSource, /teaserPendingCount/);
|
|
assert.match(driveComponentsSource, /fingerprintPendingCount/);
|
|
assert.match(driveComponentsSource, /继续生成封面/);
|
|
assert.match(driveComponentsSource, /继续生成预览视频/);
|
|
assert.match(driveComponentsSource, /继续生成指纹/);
|
|
});
|
|
|
|
test("drive cards label fingerprint count as video fingerprint count", () => {
|
|
assert.match(driveComponentsSource, /视频指纹数 \(就绪\/失败\)/);
|
|
assert.doesNotMatch(driveComponentsSource, />指纹数 \(就绪\/失败\)</);
|
|
});
|