Files
koc-loop/tests/rendered-html.test.mjs
2026-08-11 23:07:26 +08:00

459 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"),
]);
assert.match(page, /getUserFromCookieHeader/);
assert.match(page, /redirect\("\/login"\)/);
assert.match(page, /currentUser=\{user\}/);
assert.match(adminApp, /KOC LOOP/);
assert.match(adminApp, /内容分发闭环/);
assert.match(adminApp, /分发工作台/);
assert.match(adminApp, /formatShanghaiToday/);
assert.doesNotMatch(adminApp, /7月27日/);
assert.match(adminApp, /获取KOC领取链接/);
assert.match(layout, /KOC LOOP内容分发闭环/);
assert.doesNotMatch(adminApp, /codex-preview|Your site is taking shape/);
await access(new URL("../.next/standalone/server.js", import.meta.url));
await access(new URL("../.next/static", import.meta.url));
});
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'/);
});
test("ships MySQL persistence, local uploads, metadata, and no starter preview", async () => {
const [adminApp, layout, packageJson, compose, objectStore] = await Promise.all([
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"),
readFile(new URL("../docker-compose.self-hosted.yml", import.meta.url), "utf8"),
readFile(new URL("../lib/object-store.ts", import.meta.url), "utf8"),
]);
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/);
assert.match(packageJson, /"mysql2"/);
assert.match(compose, /mysql:8\.4/);
assert.match(compose, /upload_data:\/data\/koc\/uploads/);
assert.match(objectStore, /class LocalObjectStore/);
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"/);
assert.match(partnerRoute, /parseClaimantIdentifier/);
assert.match(partnerRoute, /claimantIdentifier\.canonical/);
assert.match(partnerRoute, /legacyPartnerId/);
assert.match(partnerRoute, /微信号或手机号/);
assert.match(partnerRoute, /extractXhsPublishUrl/);
assert.match(partnerRoute, /小红书长链或短链/);
assert.match(partnerRoute, /请填写发布链接/);
assert.doesNotMatch(partnerRoute, /请填写发布账号昵称和发布链接/);
assert.match(partnerRoute, /没有找到领取记录/);
assert.match(partnerRoute, /publicImageAssets/);
assert.match(partnerRoute, /withPartnerCors/);
assert.match(partnerRoute, /enrichDistributionAccount/);
assert.match(partnerRoute, /runInBackground\(enrichment/);
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\//);
assert.match(imageRoute, /publish-evidence\//);
assert.match(imageRoute, /creator-center\//);
assert.match(imageRoute, /imageKind === "publish"/);
assert.match(imageRoute, /imageKind === "creator"/);
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填写数据/);
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/);
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,
scheduler,
nextConfig,
migration,
accountMigration,
compose,
] = 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"),
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"),
]);
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, /打开小红书笔记/);
assert.match(adminApp, /target="_blank"/);
assert.match(adminApp, /noopener noreferrer/);
assert.match(adminApp, /const noteUrl = xhsPublishUrl\(item\.publish_url\)/);
assert.match(adminApp, /noteUrl \? \(/);
assert.match(adminApp, /hostname === "xhslink\.cn"/);
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, /自动追采/);
assert.match(scheduler, /0 10 \* \* \*/);
assert.match(scheduler, /backfillAccountProfiles/);
assert.match(scheduler, /Asia\/Shanghai/);
assert.match(bootstrapRoute, /backfillAccountProfiles/);
assert.match(nextConfig, /output: "standalone"/);
assert.match(migration, /latest_likes/);
assert.match(migration, /collection_runs_distribution_date_idx/);
assert.match(accountMigration, /public_account_id/);
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/);
});
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/);
assert.match(exportRoute, /consumeMcpExportToken/);
assert.match(workbook, /xl\/drawings\/drawing1\.xml/);
assert.match(workbook, /xl\/media\/image/);
assert.match(workbook, /oneCellAnchor/);
});
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"),
readFile(new URL("../mysql/0001_init.sql", import.meta.url), "utf8"),
]);
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/);
assert.match(auth, /PASSWORD_ITERATIONS = 100_000/);
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/);
assert.match(schema, /backgroundJobs/);
assert.match(runtimeSchema, /CREATE TABLE IF NOT EXISTS auth_sessions/);
assert.match(migration, /CREATE TABLE IF NOT EXISTS users/);
assert.match(migration, /users_single_super_admin_idx/);
});
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/);
assert.match(adminApp, /搜索IP地区/);
assert.match(adminApp, /搜索合作来源/);
assert.match(adminApp, /ipLocation\.includes\(ipKeyword\)/);
assert.match(adminApp, /includes\(sourceKeyword\)/);
assert.match(adminApp, /导出筛选结果/);
assert.match(adminApp, /filteredAccounts\.map\(\(item\) => item\.account\.id\)/);
assert.match(exportRoute, /小红书号\/抖音号/);
assert.match(exportRoute, /历史合作来源/);
assert.match(exportRoute, /合作社资源 · 不可直联/);
assert.match(exportRoute, /isManagerRequest/);
assert.match(exportRoute, /consumeMcpExportToken/);
assert.match(workbook, /relationships\/hyperlink/);
});
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/);
});
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"),
readFile(new URL("../mysql/0001_init.sql", import.meta.url), "utf8"),
]);
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, /部分笔记刚刚已被转派/);
assert.match(partnerRoute, /分享链接只能用于查看和回填包内任务/);
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, /合作社资源 · 不可直联/);
});