2026-07-30 12:06:41 +08:00
|
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
|
import { access, readFile } from "node:fs/promises";
|
|
|
|
|
|
import test from "node:test";
|
|
|
|
|
|
|
|
|
|
|
|
test("builds the KOC LOOP product shell", async () => {
|
|
|
|
|
|
const [page, adminApp, layout] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/page.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/layout.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
]);
|
2026-08-07 11:06:20 +08:00
|
|
|
|
assert.match(page, /getUserFromCookieHeader/);
|
|
|
|
|
|
assert.match(page, /redirect\("\/login"\)/);
|
|
|
|
|
|
assert.match(page, /currentUser=\{user\}/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(adminApp, /KOC LOOP/);
|
|
|
|
|
|
assert.match(adminApp, /内容分发闭环/);
|
|
|
|
|
|
assert.match(adminApp, /分发工作台/);
|
2026-08-05 11:40:29 +08:00
|
|
|
|
assert.match(adminApp, /formatShanghaiToday/);
|
|
|
|
|
|
assert.doesNotMatch(adminApp, /7月27日/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(adminApp, /获取KOC领取链接/);
|
|
|
|
|
|
assert.match(layout, /KOC LOOP|内容分发闭环/);
|
|
|
|
|
|
assert.doesNotMatch(adminApp, /codex-preview|Your site is taking shape/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
await access(new URL("../.next/standalone/server.js", import.meta.url));
|
|
|
|
|
|
await access(new URL("../.next/static", import.meta.url));
|
2026-07-30 12:06:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-08-07 17:08:56 +08:00
|
|
|
|
test("stacks user management and securely removes departed accounts", async () => {
|
|
|
|
|
|
const [usersPage, usersRoute, globalCss] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/users-page.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/users/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/globals.css", import.meta.url), "utf8"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
assert.match(globalCss, /\.user-management-layout\s*\{[^}]*grid-template-columns:\s*1fr/s);
|
|
|
|
|
|
assert.match(usersPage, /删除账号/);
|
|
|
|
|
|
assert.match(usersPage, /role="alertdialog"/);
|
|
|
|
|
|
assert.match(usersRoute, /body\.action === "delete"/);
|
|
|
|
|
|
assert.match(usersRoute, /不能删除当前登录账号/);
|
|
|
|
|
|
assert.match(usersRoute, /不能删除超级管理员账号/);
|
|
|
|
|
|
assert.match(usersRoute, /DELETE FROM auth_sessions WHERE user_id = \?/);
|
|
|
|
|
|
assert.match(usersRoute, /DELETE FROM users WHERE id = \? AND role <> 'super_admin'/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-08-11 23:07:26 +08:00
|
|
|
|
test("ships MySQL persistence, local uploads, metadata, and no starter preview", async () => {
|
|
|
|
|
|
const [adminApp, layout, packageJson, compose, objectStore] = await Promise.all([
|
2026-07-30 12:06:41 +08:00
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/layout.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../package.json", import.meta.url), "utf8"),
|
2026-08-11 23:07:26 +08:00
|
|
|
|
readFile(new URL("../docker-compose.self-hosted.yml", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/object-store.ts", import.meta.url), "utf8"),
|
2026-07-30 12:06:41 +08:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
assert.doesNotMatch(adminApp, /批量回填发布链接/);
|
|
|
|
|
|
assert.match(adminApp, /复制领取链接/);
|
|
|
|
|
|
assert.match(adminApp, /compressScreenshot/);
|
|
|
|
|
|
assert.match(layout, /KOC LOOP|内容分发闭环/);
|
|
|
|
|
|
assert.match(layout, /\/og\.png/);
|
|
|
|
|
|
assert.doesNotMatch(packageJson, /react-loading-skeleton/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(packageJson, /"mysql2"/);
|
|
|
|
|
|
assert.match(compose, /mysql:8\.4/);
|
|
|
|
|
|
assert.match(compose, /upload_data:\/data\/koc\/uploads/);
|
|
|
|
|
|
assert.match(objectStore, /class LocalObjectStore/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
await access(new URL("../public/og.png", import.meta.url));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("organizes distribution and recovery by task and imports the verified Feishu source", async () => {
|
|
|
|
|
|
const [adminApp, snapshotText, migration, imageMigration] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/feishu-source-snapshot.json", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../drizzle/0001_sloppy_blue_blade.sql", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../drizzle/0003_needy_doctor_strange.sql", import.meta.url), "utf8"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
const snapshot = JSON.parse(snapshotText);
|
|
|
|
|
|
|
|
|
|
|
|
assert.match(adminApp, /选择一个分发任务/);
|
|
|
|
|
|
assert.match(adminApp, /选择一个数据回收任务/);
|
|
|
|
|
|
assert.match(adminApp, /读取表格/);
|
|
|
|
|
|
assert.match(adminApp, /当前仅展示/);
|
|
|
|
|
|
assert.equal(snapshot.sheetId, "954953");
|
|
|
|
|
|
assert.equal(snapshot.rows.length, 61);
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
|
snapshot.rows.reduce((total, row) => total + row.images.length, 0),
|
|
|
|
|
|
64,
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.ok(snapshot.rows.every((row) => row.images.length > 0));
|
|
|
|
|
|
assert.match(migration, /source_sheet_id/);
|
|
|
|
|
|
assert.match(migration, /source_row/);
|
|
|
|
|
|
assert.match(imageMigration, /image_assets/);
|
|
|
|
|
|
assert.match(imageMigration, /claims_task_partner_created_idx/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("issues external task links and supports one-to-one note submissions", async () => {
|
|
|
|
|
|
const [
|
|
|
|
|
|
adminApp,
|
|
|
|
|
|
actionRoute,
|
|
|
|
|
|
partnerRoute,
|
|
|
|
|
|
uploadRoute,
|
|
|
|
|
|
imageRoute,
|
|
|
|
|
|
imageUploadRoute,
|
|
|
|
|
|
cors,
|
|
|
|
|
|
migration,
|
|
|
|
|
|
accountEnrichment,
|
|
|
|
|
|
partnerUtils,
|
|
|
|
|
|
publishUrlUtils,
|
|
|
|
|
|
creatorScreenshotRoute,
|
|
|
|
|
|
] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/partner/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/partner-upload/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/partner-image/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/content-image-upload/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/partner-cors.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../drizzle/0002_wealthy_maelstrom.sql", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/account-enrichment-service.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/partner-utils.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/publish-url.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/creator-screenshot/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
assert.match(adminApp, /复制领取链接/);
|
|
|
|
|
|
assert.match(adminApp, /小红书号/);
|
|
|
|
|
|
assert.match(adminApp, /public_account_id/);
|
|
|
|
|
|
assert.doesNotMatch(adminApp, /title=\{account\.platform_uid\}/);
|
|
|
|
|
|
assert.doesNotMatch(adminApp, /平均阅读/);
|
|
|
|
|
|
assert.doesNotMatch(adminApp, /批量回填发布链接/);
|
|
|
|
|
|
assert.doesNotMatch(actionRoute, /submit_links/);
|
|
|
|
|
|
assert.match(partnerRoute, /publish_screenshot_key/);
|
|
|
|
|
|
assert.match(partnerRoute, /creator_screenshot_key/);
|
|
|
|
|
|
assert.match(partnerRoute, /submit_creator_metrics/);
|
|
|
|
|
|
assert.match(partnerRoute, /ocr_status = 'manual'/);
|
|
|
|
|
|
assert.match(partnerRoute, /请填写正确的曝光量和阅读量/);
|
|
|
|
|
|
assert.match(partnerRoute, /笔记与领取凭证不匹配/);
|
|
|
|
|
|
assert.match(partnerRoute, /action === "recover"/);
|
2026-08-03 13:48:01 +08:00
|
|
|
|
assert.match(partnerRoute, /parseClaimantIdentifier/);
|
|
|
|
|
|
assert.match(partnerRoute, /claimantIdentifier\.canonical/);
|
|
|
|
|
|
assert.match(partnerRoute, /legacyPartnerId/);
|
|
|
|
|
|
assert.match(partnerRoute, /微信号或手机号/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(partnerRoute, /extractXhsPublishUrl/);
|
|
|
|
|
|
assert.match(partnerRoute, /小红书长链或短链/);
|
2026-08-05 11:40:29 +08:00
|
|
|
|
assert.match(partnerRoute, /请填写发布链接/);
|
|
|
|
|
|
assert.doesNotMatch(partnerRoute, /请填写发布账号昵称和发布链接/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(partnerRoute, /没有找到领取记录/);
|
|
|
|
|
|
assert.match(partnerRoute, /publicImageAssets/);
|
|
|
|
|
|
assert.match(partnerRoute, /withPartnerCors/);
|
|
|
|
|
|
assert.match(partnerRoute, /enrichDistributionAccount/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(partnerRoute, /runInBackground\(enrichment/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(accountEnrichment, /profile_url = excluded\.profile_url/);
|
|
|
|
|
|
assert.match(accountEnrichment, /resolveXhsAccountProfileFromMcp/);
|
|
|
|
|
|
assert.match(accountEnrichment, /resolveXhsProfileDetailsFromMcp/);
|
|
|
|
|
|
assert.match(accountEnrichment, /isVerifiedXhsProfileUrl/);
|
|
|
|
|
|
assert.match(accountEnrichment, /endsWith\("\.xiaohongshu\.com"\)/);
|
|
|
|
|
|
assert.match(accountEnrichment, /DELETE FROM accounts/);
|
|
|
|
|
|
assert.match(accountEnrichment, /a\.followers/);
|
|
|
|
|
|
assert.match(accountEnrichment, /followers = CASE/);
|
|
|
|
|
|
assert.match(accountEnrichment, /Number\(row\.followers \?\? 0\) === 0/);
|
|
|
|
|
|
assert.match(partnerUtils, /pending-\$\{hashText/);
|
|
|
|
|
|
assert.match(partnerUtils, /profileUrl:\s*""/);
|
|
|
|
|
|
assert.match(publishUrlUtils, /xhslink\.cn/);
|
|
|
|
|
|
assert.doesNotMatch(partnerUtils, /user\/profile\/\$\{platformUid\}/);
|
|
|
|
|
|
assert.match(uploadRoute, /publish-evidence/);
|
|
|
|
|
|
assert.match(uploadRoute, /creator-center/);
|
|
|
|
|
|
assert.match(uploadRoute, /ELSE 'uploaded'/);
|
|
|
|
|
|
assert.doesNotMatch(uploadRoute, /ocrMetric/);
|
|
|
|
|
|
assert.match(uploadRoute, /x-koc-upload-kind/);
|
|
|
|
|
|
assert.match(uploadRoute, /x-koc-distribution/);
|
|
|
|
|
|
assert.match(uploadRoute, /request\.arrayBuffer/);
|
|
|
|
|
|
assert.match(imageRoute, /content-assets\//);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(imageRoute, /publish-evidence\//);
|
|
|
|
|
|
assert.match(imageRoute, /creator-center\//);
|
|
|
|
|
|
assert.match(imageRoute, /imageKind === "publish"/);
|
|
|
|
|
|
assert.match(imageRoute, /imageKind === "creator"/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(imageRoute, /cl\.claim_token/);
|
|
|
|
|
|
assert.match(imageUploadRoute, /isAdminRequest/);
|
|
|
|
|
|
assert.match(cors, /KOC_PORTAL_URL/);
|
|
|
|
|
|
assert.match(cors, /X-KOC-Distribution/);
|
|
|
|
|
|
assert.match(cors, /X-KOC-Upload-Kind/);
|
|
|
|
|
|
assert.match(cors, /Access-Control-Allow-Origin/);
|
|
|
|
|
|
assert.match(adminApp, /hasCreatorMetrics/);
|
|
|
|
|
|
assert.match(adminApp, /待KOC填写数据/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(adminApp, /AdminImageLightbox/);
|
|
|
|
|
|
assert.match(adminApp, /CreatorScreenshotPreview/);
|
|
|
|
|
|
assert.match(adminApp, /admin-image-lightbox/);
|
|
|
|
|
|
assert.match(adminApp, /aria-label="上一张"/);
|
|
|
|
|
|
assert.match(adminApp, /aria-label="下一张"/);
|
|
|
|
|
|
assert.match(adminApp, /event\.key === "ArrowLeft"/);
|
|
|
|
|
|
assert.match(adminApp, /event\.key === "ArrowRight"/);
|
|
|
|
|
|
assert.match(adminApp, /activeIndex \+ 1/);
|
|
|
|
|
|
assert.doesNotMatch(adminApp, /href=\{`\/api\/task-result-image/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(creatorScreenshotRoute, /getUploadBucket/);
|
|
|
|
|
|
assert.match(creatorScreenshotRoute, /Content-Disposition/);
|
|
|
|
|
|
assert.match(creatorScreenshotRoute, /isAdminRequest/);
|
|
|
|
|
|
assert.match(migration, /share_token/);
|
|
|
|
|
|
assert.match(migration, /claim_token/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("supports task collection schedules and latest public metrics", async () => {
|
|
|
|
|
|
const [
|
|
|
|
|
|
adminApp,
|
|
|
|
|
|
actionRoute,
|
|
|
|
|
|
bootstrapRoute,
|
|
|
|
|
|
collectionService,
|
2026-08-11 23:07:26 +08:00
|
|
|
|
scheduler,
|
|
|
|
|
|
nextConfig,
|
2026-07-30 12:06:41 +08:00
|
|
|
|
migration,
|
|
|
|
|
|
accountMigration,
|
2026-08-11 23:07:26 +08:00
|
|
|
|
compose,
|
2026-07-30 12:06:41 +08:00
|
|
|
|
] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/bootstrap/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/collection-service.ts", import.meta.url), "utf8"),
|
2026-08-11 23:07:26 +08:00
|
|
|
|
readFile(new URL("../lib/scheduler.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../next.config.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../mysql/0001_init.sql", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../mysql/0001_init.sql", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../docker-compose.self-hosted.yml", import.meta.url), "utf8"),
|
2026-07-30 12:06:41 +08:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
for (const label of [
|
|
|
|
|
|
"点赞",
|
|
|
|
|
|
"收藏",
|
|
|
|
|
|
"评论",
|
|
|
|
|
|
"总互动",
|
|
|
|
|
|
"数据更新时间",
|
|
|
|
|
|
"采集状态",
|
|
|
|
|
|
"发布时间",
|
|
|
|
|
|
]) {
|
|
|
|
|
|
assert.match(adminApp, new RegExp(label));
|
|
|
|
|
|
}
|
|
|
|
|
|
assert.match(adminApp, /选择开始日期与采集日/);
|
|
|
|
|
|
assert.match(adminApp, /第1天到第7天/);
|
|
|
|
|
|
assert.match(adminApp, /内容 \/ 发布账号/);
|
|
|
|
|
|
assert.match(adminApp, /recovery-title-link/);
|
|
|
|
|
|
assert.match(adminApp, /打开小红书笔记/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(adminApp, /target="_blank"/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(adminApp, /noopener noreferrer/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(adminApp, /const noteUrl = xhsPublishUrl\(item\.publish_url\)/);
|
|
|
|
|
|
assert.match(adminApp, /noteUrl \? \(/);
|
|
|
|
|
|
assert.match(adminApp, /hostname === "xhslink\.cn"/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(actionRoute, /save_collection_schedule/);
|
|
|
|
|
|
assert.match(actionRoute, /collect_now/);
|
|
|
|
|
|
assert.match(actionRoute, /backfill_account_profiles/);
|
|
|
|
|
|
assert.match(actionRoute, /set_public_account_ids/);
|
|
|
|
|
|
assert.match(actionRoute, /run_due_collections/);
|
|
|
|
|
|
assert.match(actionRoute, /retry_failed_collections/);
|
|
|
|
|
|
assert.match(actionRoute, /createCollectionRunTasks/);
|
|
|
|
|
|
assert.match(bootstrapRoute, /runDueScheduledCollections/);
|
|
|
|
|
|
assert.match(collectionService, /INSERT OR IGNORE INTO collection_runs/);
|
|
|
|
|
|
assert.match(collectionService, /collectXhsMetricsFromMcp/);
|
|
|
|
|
|
assert.doesNotMatch(collectionService, /hashText/);
|
|
|
|
|
|
assert.match(collectionService, /runScheduledCollections/);
|
|
|
|
|
|
assert.match(collectionService, /runDueScheduledCollections/);
|
|
|
|
|
|
assert.match(collectionService, /retryFailedCollections/);
|
|
|
|
|
|
assert.match(collectionService, /exposure IS NOT NULL AND views IS NOT NULL/);
|
|
|
|
|
|
assert.match(collectionService, /等待第\$\{scheduleDay\}天 10:00自动采集/);
|
|
|
|
|
|
assert.doesNotMatch(collectionService, /latestDueSchedule/);
|
|
|
|
|
|
assert.match(collectionService, /自动追采/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(scheduler, /0 10 \* \* \*/);
|
|
|
|
|
|
assert.match(scheduler, /backfillAccountProfiles/);
|
|
|
|
|
|
assert.match(scheduler, /Asia\/Shanghai/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(bootstrapRoute, /backfillAccountProfiles/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(nextConfig, /output: "standalone"/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(migration, /latest_likes/);
|
|
|
|
|
|
assert.match(migration, /collection_runs_distribution_date_idx/);
|
|
|
|
|
|
assert.match(accountMigration, /public_account_id/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(compose, /ENABLE_SCHEDULER/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("supports fixed screenshot collection tasks without publish metrics", async () => {
|
|
|
|
|
|
const [adminApp, actionRoute, partnerRoute, uploadRoute, migration, multiMigration, exportRoute] =
|
|
|
|
|
|
await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/partner/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/partner-upload/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../mysql/0003_screenshot_tasks.sql", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../mysql/0004_multi_result_screenshots.sql", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/screenshot-task-export/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
assert.match(adminApp, /内容发布/);
|
|
|
|
|
|
assert.match(adminApp, /截图回收/);
|
|
|
|
|
|
assert.match(adminApp, /小红书搜索关键词/);
|
|
|
|
|
|
assert.match(adminApp, /批量下载截图/);
|
|
|
|
|
|
assert.match(actionRoute, /create_screenshot_task/);
|
|
|
|
|
|
assert.match(partnerRoute, /submit_screenshot_result/);
|
|
|
|
|
|
assert.match(partnerRoute, /截图回收任务无需填写发布链接/);
|
|
|
|
|
|
assert.match(uploadRoute, /task-results\//);
|
|
|
|
|
|
assert.match(uploadRoute, /task-result/);
|
|
|
|
|
|
assert.match(uploadRoute, /MAX_RESULT_SCREENSHOTS/);
|
|
|
|
|
|
assert.match(migration, /task_type/);
|
|
|
|
|
|
assert.match(migration, /result_screenshot_key/);
|
|
|
|
|
|
assert.match(multiMigration, /MODIFY COLUMN result_screenshot_key TEXT/);
|
|
|
|
|
|
assert.match(adminApp, /admin-result-gallery/);
|
|
|
|
|
|
assert.match(exportRoute, /回收清单\.csv/);
|
|
|
|
|
|
assert.match(exportRoute, /application\/zip/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-08-05 11:40:29 +08:00
|
|
|
|
test("exports complete task recovery data to Excel with embedded images", async () => {
|
|
|
|
|
|
const [adminApp, exportRoute, workbook] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/recovery-export/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/recovery-workbook.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
assert.match(adminApp, /导出全部数据/);
|
|
|
|
|
|
assert.match(adminApp, /\/api\/recovery-export/);
|
|
|
|
|
|
assert.match(adminApp, /图片和截图已嵌入表格/);
|
|
|
|
|
|
assert.match(exportRoute, /小红书昵称/);
|
|
|
|
|
|
assert.match(exportRoute, /曝光量-实际(第7天)/);
|
|
|
|
|
|
assert.match(exportRoute, /阅读量-实际(第7天)/);
|
|
|
|
|
|
assert.match(exportRoute, /publish_screenshot_key/);
|
|
|
|
|
|
assert.match(exportRoute, /screenshot_key/);
|
|
|
|
|
|
assert.match(exportRoute, /image_assets/);
|
|
|
|
|
|
assert.match(exportRoute, /isAdminRequest/);
|
2026-08-07 14:12:52 +08:00
|
|
|
|
assert.match(exportRoute, /consumeMcpExportToken/);
|
2026-08-05 11:40:29 +08:00
|
|
|
|
assert.match(workbook, /xl\/drawings\/drawing1\.xml/);
|
|
|
|
|
|
assert.match(workbook, /xl\/media\/image/);
|
|
|
|
|
|
assert.match(workbook, /oneCellAnchor/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-08-07 11:06:20 +08:00
|
|
|
|
test("provides simple username-password login and three server-enforced roles", async () => {
|
|
|
|
|
|
const [
|
|
|
|
|
|
adminApp,
|
|
|
|
|
|
usersPage,
|
|
|
|
|
|
loginPage,
|
|
|
|
|
|
loginRoute,
|
|
|
|
|
|
logoutRoute,
|
|
|
|
|
|
usersRoute,
|
|
|
|
|
|
auth,
|
|
|
|
|
|
bootstrapRoute,
|
|
|
|
|
|
resourceExportRoute,
|
|
|
|
|
|
schema,
|
|
|
|
|
|
runtimeSchema,
|
|
|
|
|
|
migration,
|
|
|
|
|
|
] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/users-page.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/login/page.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/auth/login/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/auth/logout/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/users/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/user-auth.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/bootstrap/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/resources-export/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../db/schema.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/mvp-db.ts", import.meta.url), "utf8"),
|
2026-08-11 23:07:26 +08:00
|
|
|
|
readFile(new URL("../mysql/0001_init.sql", import.meta.url), "utf8"),
|
2026-08-07 11:06:20 +08:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
assert.match(loginPage, /登录账号/);
|
|
|
|
|
|
assert.match(loginPage, /当前|管理员分配|账号和密码/);
|
|
|
|
|
|
assert.match(loginRoute, /ensureInitialSuperAdmin/);
|
|
|
|
|
|
assert.match(loginRoute, /verifyPassword/);
|
|
|
|
|
|
assert.match(loginRoute, /Set-Cookie/);
|
|
|
|
|
|
assert.match(logoutRoute, /deleteSession/);
|
|
|
|
|
|
assert.match(logoutRoute, /clearSessionCookie/);
|
|
|
|
|
|
assert.match(auth, /PBKDF2/);
|
2026-08-07 11:20:31 +08:00
|
|
|
|
assert.match(auth, /PASSWORD_ITERATIONS = 100_000/);
|
2026-08-07 11:06:20 +08:00
|
|
|
|
assert.match(auth, /HttpOnly/);
|
|
|
|
|
|
assert.match(auth, /SameSite=Lax/);
|
|
|
|
|
|
assert.match(auth, /SELECT id FROM users WHERE role = 'super_admin' LIMIT 1/);
|
|
|
|
|
|
assert.match(usersRoute, /currentUser\.role !== "super_admin" && role !== "user"/);
|
|
|
|
|
|
assert.match(usersRoute, /DELETE FROM auth_sessions WHERE user_id =/);
|
|
|
|
|
|
assert.match(usersPage, /不开放注册和个人改密/);
|
|
|
|
|
|
assert.match(usersPage, /普通用户/);
|
|
|
|
|
|
assert.match(usersPage, /管理员/);
|
|
|
|
|
|
assert.match(adminApp, /item\.key === "resources" && currentUser\.role === "user"/);
|
|
|
|
|
|
assert.match(adminApp, /item\.key === "users" && !isManager/);
|
|
|
|
|
|
assert.match(bootstrapRoute, /principal\.user\.role === "user"/);
|
|
|
|
|
|
assert.match(bootstrapRoute, /accounts: \[\]/);
|
|
|
|
|
|
assert.match(resourceExportRoute, /isManagerRequest/);
|
|
|
|
|
|
assert.match(schema, /authSessions/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(schema, /backgroundJobs/);
|
2026-08-07 11:06:20 +08:00
|
|
|
|
assert.match(runtimeSchema, /CREATE TABLE IF NOT EXISTS auth_sessions/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(migration, /CREATE TABLE IF NOT EXISTS users/);
|
2026-08-07 11:06:20 +08:00
|
|
|
|
assert.match(migration, /users_single_super_admin_idx/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-08-06 10:18:23 +08:00
|
|
|
|
test("filters and exports the current KOC resource result set", async () => {
|
|
|
|
|
|
const [adminApp, exportRoute, workbook] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/resources-export/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/recovery-workbook.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
assert.match(adminApp, /搜索账号名称 \/ 账号ID/);
|
2026-08-06 11:48:10 +08:00
|
|
|
|
assert.match(adminApp, /搜索IP地区/);
|
|
|
|
|
|
assert.match(adminApp, /搜索合作来源/);
|
|
|
|
|
|
assert.match(adminApp, /ipLocation\.includes\(ipKeyword\)/);
|
|
|
|
|
|
assert.match(adminApp, /includes\(sourceKeyword\)/);
|
2026-08-06 10:18:23 +08:00
|
|
|
|
assert.match(adminApp, /导出筛选结果/);
|
|
|
|
|
|
assert.match(adminApp, /filteredAccounts\.map\(\(item\) => item\.account\.id\)/);
|
|
|
|
|
|
assert.match(exportRoute, /小红书号\/抖音号/);
|
|
|
|
|
|
assert.match(exportRoute, /历史合作来源/);
|
|
|
|
|
|
assert.match(exportRoute, /合作社资源 · 不可直联/);
|
2026-08-07 11:06:20 +08:00
|
|
|
|
assert.match(exportRoute, /isManagerRequest/);
|
2026-08-07 14:12:52 +08:00
|
|
|
|
assert.match(exportRoute, /consumeMcpExportToken/);
|
2026-08-06 10:18:23 +08:00
|
|
|
|
assert.match(workbook, /relationships\/hyperlink/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-08-11 23:07:26 +08:00
|
|
|
|
test("imports existing KOC resources through a validated spreadsheet preview", async () => {
|
|
|
|
|
|
const [adminApp, importRoute, resourceParser, accountMigration] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/resources-import/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/resource-import.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../mysql/0002_resource_import.sql", import.meta.url), "utf8"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
assert.match(adminApp, /下载导入模板/);
|
|
|
|
|
|
assert.match(adminApp, /校验并预览/);
|
|
|
|
|
|
assert.match(adminApp, /确认导入/);
|
|
|
|
|
|
assert.match(importRoute, /isManagerRequest/);
|
|
|
|
|
|
assert.match(importRoute, /mode !== "commit"/);
|
|
|
|
|
|
assert.match(resourceParser, /RESOURCE_IMPORT_MAX_ROWS = 100/);
|
|
|
|
|
|
assert.match(resourceParser, /当前自动解析仅支持小红书账号主页/);
|
|
|
|
|
|
assert.match(importRoute, /resolveXhsProfileDetailsFromMcp/);
|
|
|
|
|
|
assert.match(accountMigration, /cooperation_source/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-30 12:06:41 +08:00
|
|
|
|
test("supports anonymous partner delegation without creating a second data flow", async () => {
|
|
|
|
|
|
const [
|
|
|
|
|
|
adminApp,
|
|
|
|
|
|
partnerRoute,
|
|
|
|
|
|
uploadRoute,
|
|
|
|
|
|
imageRoute,
|
|
|
|
|
|
cors,
|
|
|
|
|
|
schema,
|
|
|
|
|
|
runtimeSchema,
|
|
|
|
|
|
migration,
|
|
|
|
|
|
] = await Promise.all([
|
|
|
|
|
|
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/partner/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/partner-upload/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../app/api/partner-image/route.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/partner-cors.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../db/schema.ts", import.meta.url), "utf8"),
|
|
|
|
|
|
readFile(new URL("../lib/mvp-db.ts", import.meta.url), "utf8"),
|
2026-08-11 23:07:26 +08:00
|
|
|
|
readFile(new URL("../mysql/0001_init.sql", import.meta.url), "utf8"),
|
2026-07-30 12:06:41 +08:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
assert.match(schema, /delegationBundles/);
|
|
|
|
|
|
assert.match(schema, /delegationBundleId/);
|
|
|
|
|
|
assert.match(runtimeSchema, /CREATE TABLE IF NOT EXISTS delegation_bundles/);
|
|
|
|
|
|
assert.match(migration, /delegation_bundles/);
|
|
|
|
|
|
assert.match(migration, /delegation_bundle_id/);
|
|
|
|
|
|
assert.match(partnerRoute, /action === "create_delegation"/);
|
|
|
|
|
|
assert.match(partnerRoute, /action === "revoke_delegation"/);
|
|
|
|
|
|
assert.match(partnerRoute, /findAccessibleAssignment/);
|
|
|
|
|
|
assert.match(partnerRoute, /b\.status = 'active'/);
|
|
|
|
|
|
assert.match(partnerRoute, /部分笔记刚刚已被转派/);
|
2026-08-11 23:07:26 +08:00
|
|
|
|
assert.match(partnerRoute, /分享链接只能用于查看和回填包内任务/);
|
2026-07-30 12:06:41 +08:00
|
|
|
|
assert.match(partnerRoute, /private, no-store/);
|
|
|
|
|
|
assert.match(uploadRoute, /x-koc-delegation/);
|
|
|
|
|
|
assert.match(uploadRoute, /delegation_bundles/);
|
|
|
|
|
|
assert.match(imageRoute, /delegation_bundles/);
|
|
|
|
|
|
assert.match(cors, /X-KOC-Delegation/);
|
|
|
|
|
|
assert.match(adminApp, /合作社资源 · 不可直联/);
|
|
|
|
|
|
});
|