698 lines
20 KiB
Markdown
698 lines
20 KiB
Markdown
# Market Batch Submit Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Add a standalone `提交批次` toolbar action that collects the currently selected export range of Xingtu creators, prompts for a batch name, builds a batch payload with Logto user identity plus creator IDs, and submits the batch to a protected API endpoint with a Bearer token.
|
|
|
|
**Architecture:** Reuse the existing export-range collection path so multi-page batch submission behaves exactly like CSV export. Keep responsibilities split: toolbar UI emits a submit intent, a batch-payload module assembles the request body, and a protected batch-submit client owns token injection plus POST behavior. For first-pass verification, extend the local mock server with a batch endpoint that echoes the submitted payload.
|
|
|
|
**Tech Stack:** Chrome MV3, TypeScript, Vitest, tsup, Node HTTP server, `@logto/chrome-extension`
|
|
|
|
---
|
|
|
|
## File Map
|
|
|
|
### New files
|
|
|
|
- `src/content/market/batch-payload.ts`
|
|
- `src/shared/batch-submit-client.ts`
|
|
- `tests/batch-payload.test.ts`
|
|
- `tests/batch-submit-client.test.ts`
|
|
|
|
### Existing files to modify
|
|
|
|
- `src/content/market/plugin-toolbar.ts`
|
|
- `src/content/market/index.ts`
|
|
- `src/shared/auth-messages.ts`
|
|
- `src/background/auth/state.ts`
|
|
- `src/popup/view.ts`
|
|
- `scripts/mock-protected-api.mjs`
|
|
- `tests/market-content-entry.test.ts`
|
|
- `tests/popup-entry.test.ts`
|
|
- `tests/mock-protected-api.test.ts`
|
|
- `README.md`
|
|
|
|
### Responsibilities
|
|
|
|
- `src/content/market/plugin-toolbar.ts`: add a dedicated submit button and include it in busy-state handling.
|
|
- `src/content/market/index.ts`: orchestrate prompt input, range collection reuse, payload creation, and batch submission.
|
|
- `src/content/market/batch-payload.ts`: create the batch payload from batch name, timestamp, auth state, and market records.
|
|
- `src/shared/batch-submit-client.ts`: POST JSON to the protected batch endpoint with `Authorization: Bearer <token>`.
|
|
- `src/shared/auth-messages.ts` and `src/background/auth/state.ts`: expose stable access to Logto user `sub` and display name already present in auth state.
|
|
- `scripts/mock-protected-api.mjs`: add a local `/api/mock/batches` endpoint that validates Bearer auth and echoes the payload.
|
|
|
|
## Task 1: Add The Batch Payload Builder
|
|
|
|
**Files:**
|
|
- Create: `src/content/market/batch-payload.ts`
|
|
- Create: `tests/batch-payload.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing batch payload tests**
|
|
|
|
Create `tests/batch-payload.test.ts` with cases for:
|
|
|
|
```ts
|
|
import { describe, expect, test } from "vitest";
|
|
|
|
import { createBatchPayload } from "../src/content/market/batch-payload";
|
|
|
|
describe("batch-payload", () => {
|
|
test("builds a batch id from the batch name and timestamp", () => {
|
|
const payload = createBatchPayload({
|
|
authState: {
|
|
isAuthenticated: true,
|
|
resource: "https://talent-search.intelligrow.cn",
|
|
userInfo: {
|
|
name: "王少卿",
|
|
sub: "p7pdhhtde8kj"
|
|
}
|
|
},
|
|
batchName: "618达人筛选第一批",
|
|
createdAt: "2026-04-22T12:30:00.000Z",
|
|
records: [
|
|
{ authorId: "111", authorName: "达人A", status: "success" },
|
|
{ authorId: "222", authorName: "达人B", status: "success" }
|
|
]
|
|
});
|
|
|
|
expect(payload).toEqual({
|
|
authors: [
|
|
{ authorId: "111", authorName: "达人A" },
|
|
{ authorId: "222", authorName: "达人B" }
|
|
],
|
|
batchId: "618达人筛选第一批-2026-04-22T12:30:00.000Z",
|
|
batchName: "618达人筛选第一批",
|
|
createdAt: "2026-04-22T12:30:00.000Z",
|
|
creatorName: "王少卿",
|
|
logtoUserId: "p7pdhhtde8kj",
|
|
resource: "https://talent-search.intelligrow.cn"
|
|
});
|
|
});
|
|
|
|
test("throws when the user id is unavailable", () => {
|
|
expect(() =>
|
|
createBatchPayload({
|
|
authState: {
|
|
isAuthenticated: true,
|
|
resource: "https://talent-search.intelligrow.cn",
|
|
userInfo: {
|
|
name: "王少卿"
|
|
}
|
|
},
|
|
batchName: "批次A",
|
|
createdAt: "2026-04-22T12:30:00.000Z",
|
|
records: [{ authorId: "111", authorName: "达人A", status: "success" }]
|
|
})
|
|
).toThrow(/user/i);
|
|
});
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the batch payload test to verify red**
|
|
|
|
Run: `npm test -- tests/batch-payload.test.ts`
|
|
|
|
Expected: FAIL because `createBatchPayload` does not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal batch payload builder**
|
|
|
|
Create `src/content/market/batch-payload.ts` with:
|
|
|
|
```ts
|
|
import type { AuthStateValue } from "../../shared/auth-messages";
|
|
import type { MarketRecord } from "./types";
|
|
|
|
export interface BatchPayload {
|
|
authors: Array<{
|
|
authorId: string;
|
|
authorName: string;
|
|
}>;
|
|
batchId: string;
|
|
batchName: string;
|
|
createdAt: string;
|
|
creatorName: string;
|
|
logtoUserId: string;
|
|
resource: string;
|
|
}
|
|
|
|
export function createBatchPayload(options: {
|
|
authState: AuthStateValue;
|
|
batchName: string;
|
|
createdAt: string;
|
|
records: MarketRecord[];
|
|
}): BatchPayload {
|
|
const logtoUserId = options.authState.userInfo?.sub?.trim();
|
|
if (!logtoUserId) {
|
|
throw new Error("batch submit user id unavailable");
|
|
}
|
|
|
|
const resource = options.authState.resource?.trim();
|
|
if (!resource) {
|
|
throw new Error("batch submit resource unavailable");
|
|
}
|
|
|
|
const batchName = options.batchName.trim();
|
|
if (!batchName) {
|
|
throw new Error("batch submit batch name is required");
|
|
}
|
|
|
|
return {
|
|
authors: options.records.map((record) => ({
|
|
authorId: record.authorId,
|
|
authorName: record.authorName
|
|
})),
|
|
batchId: `${batchName}-${options.createdAt}`,
|
|
batchName,
|
|
createdAt: options.createdAt,
|
|
creatorName:
|
|
options.authState.userInfo?.name ??
|
|
options.authState.userInfo?.username ??
|
|
logtoUserId,
|
|
logtoUserId,
|
|
resource
|
|
};
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Run the batch payload test to verify green**
|
|
|
|
Run: `npm test -- tests/batch-payload.test.ts`
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Commit the payload builder slice**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add src/content/market/batch-payload.ts tests/batch-payload.test.ts
|
|
git commit -m "feat: add batch payload builder"
|
|
```
|
|
|
|
## Task 2: Build The Protected Batch Submit Client
|
|
|
|
**Files:**
|
|
- Create: `src/shared/batch-submit-client.ts`
|
|
- Create: `tests/batch-submit-client.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing batch submit client tests**
|
|
|
|
Create `tests/batch-submit-client.test.ts` with:
|
|
|
|
```ts
|
|
import { describe, expect, test, vi } from "vitest";
|
|
|
|
import { createBatchSubmitClient } from "../src/shared/batch-submit-client";
|
|
|
|
describe("batch-submit-client", () => {
|
|
test("posts the batch payload with a Bearer token", async () => {
|
|
const sendMessage = vi.fn(async () => ({
|
|
ok: true,
|
|
type: "auth:token",
|
|
value: { accessToken: "abc123" }
|
|
}));
|
|
const fetchImpl = vi.fn(async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ ok: true, acceptedCount: 2 })
|
|
}));
|
|
|
|
const client = createBatchSubmitClient({
|
|
baseUrl: "http://127.0.0.1:4319",
|
|
fetchImpl,
|
|
sendMessage
|
|
});
|
|
|
|
await client.submitBatch({
|
|
authors: [{ authorId: "111", authorName: "达人A" }],
|
|
batchId: "批次A-2026-04-22T12:30:00.000Z",
|
|
batchName: "批次A",
|
|
createdAt: "2026-04-22T12:30:00.000Z",
|
|
creatorName: "王少卿",
|
|
logtoUserId: "p7pdhhtde8kj",
|
|
resource: "https://talent-search.intelligrow.cn"
|
|
});
|
|
|
|
expect(fetchImpl).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:4319/api/mock/batches",
|
|
expect.objectContaining({
|
|
body: JSON.stringify(
|
|
expect.objectContaining({
|
|
batchId: "批次A-2026-04-22T12:30:00.000Z"
|
|
})
|
|
),
|
|
headers: expect.objectContaining({
|
|
Authorization: "Bearer abc123",
|
|
"Content-Type": "application/json"
|
|
}),
|
|
method: "POST"
|
|
})
|
|
);
|
|
});
|
|
|
|
test("throws on unauthorized responses", async () => {
|
|
const client = createBatchSubmitClient({
|
|
baseUrl: "http://127.0.0.1:4319",
|
|
fetchImpl: vi.fn(async () => ({
|
|
ok: false,
|
|
status: 401,
|
|
json: async () => ({ ok: false, error: "unauthorized" })
|
|
})),
|
|
sendMessage: vi.fn(async () => ({
|
|
ok: true,
|
|
type: "auth:token",
|
|
value: { accessToken: "abc123" }
|
|
}))
|
|
});
|
|
|
|
await expect(
|
|
client.submitBatch({
|
|
authors: [],
|
|
batchId: "批次A-2026-04-22T12:30:00.000Z",
|
|
batchName: "批次A",
|
|
createdAt: "2026-04-22T12:30:00.000Z",
|
|
creatorName: "王少卿",
|
|
logtoUserId: "p7pdhhtde8kj",
|
|
resource: "https://talent-search.intelligrow.cn"
|
|
})
|
|
).rejects.toThrow(/unauthorized/i);
|
|
});
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the batch submit client test to verify red**
|
|
|
|
Run: `npm test -- tests/batch-submit-client.test.ts`
|
|
|
|
Expected: FAIL because `createBatchSubmitClient` does not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal batch submit client**
|
|
|
|
Create `src/shared/batch-submit-client.ts` with:
|
|
|
|
```ts
|
|
import { isAuthResponseMessage } from "./auth-messages";
|
|
import type { BatchPayload } from "../content/market/batch-payload";
|
|
|
|
interface FetchResponseLike {
|
|
json(): Promise<unknown>;
|
|
ok: boolean;
|
|
status: number;
|
|
}
|
|
|
|
type FetchLike = (
|
|
input: string,
|
|
init?: RequestInit
|
|
) => Promise<FetchResponseLike>;
|
|
|
|
type SendMessageLike = (message: unknown) => Promise<unknown>;
|
|
|
|
export function createBatchSubmitClient(options: {
|
|
baseUrl: string;
|
|
fetchImpl?: FetchLike;
|
|
sendMessage: SendMessageLike;
|
|
}) {
|
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
|
|
return {
|
|
async submitBatch(payload: BatchPayload) {
|
|
const token = await readAccessToken(options.sendMessage);
|
|
const response = await fetchImpl(
|
|
new URL("/api/mock/batches", options.baseUrl).toString(),
|
|
{
|
|
body: JSON.stringify(payload),
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
"Content-Type": "application/json"
|
|
},
|
|
method: "POST"
|
|
}
|
|
);
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
throw new Error("batch submit unauthorized");
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`batch submit failed: ${response.status}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
};
|
|
}
|
|
|
|
async function readAccessToken(sendMessage: SendMessageLike): Promise<string> {
|
|
const response = await sendMessage({ type: "auth:get-access-token" });
|
|
if (
|
|
!isAuthResponseMessage(response) ||
|
|
!response.ok ||
|
|
response.type !== "auth:token" ||
|
|
!response.value.accessToken.trim()
|
|
) {
|
|
throw new Error("batch submit token unavailable");
|
|
}
|
|
return response.value.accessToken;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Run the batch submit client test to verify green**
|
|
|
|
Run: `npm test -- tests/batch-submit-client.test.ts`
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Commit the batch submit client slice**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add src/shared/batch-submit-client.ts tests/batch-submit-client.test.ts
|
|
git commit -m "feat: add batch submit client"
|
|
```
|
|
|
|
## Task 3: Extend The Toolbar With A Batch Submit Action
|
|
|
|
**Files:**
|
|
- Modify: `src/content/market/plugin-toolbar.ts`
|
|
- Modify: `tests/market-content-entry.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing toolbar tests**
|
|
|
|
Add tests to `tests/market-content-entry.test.ts` for:
|
|
|
|
```ts
|
|
test("renders a batch submit button in the toolbar", async () => {
|
|
document.body.innerHTML = buildMarketFixture();
|
|
|
|
const { createMarketController } = await import("../src/content/market/index");
|
|
const controller = createMarketController({
|
|
document,
|
|
loadAuthorMetrics: async () => ({
|
|
success: true,
|
|
rates: {
|
|
personalVideoAfterSearchRate: "0.01% - 0.1%",
|
|
singleVideoAfterSearchRate: "0.01% - 0.1%"
|
|
}
|
|
}),
|
|
window
|
|
});
|
|
|
|
await controller.ready;
|
|
|
|
expect(document.querySelector('[data-plugin-batch-submit="button"]')).not.toBeNull();
|
|
});
|
|
```
|
|
|
|
Also add a test that `setToolbarBusyState()` disables the batch submit button.
|
|
|
|
- [ ] **Step 2: Run the focused market toolbar tests to verify red**
|
|
|
|
Run: `npm test -- tests/market-content-entry.test.ts`
|
|
|
|
Expected: FAIL because the batch submit button does not exist yet
|
|
|
|
- [ ] **Step 3: Add the minimal toolbar button**
|
|
|
|
Update `src/content/market/plugin-toolbar.ts`:
|
|
|
|
- extend `PluginToolbarHandlers` with `onSubmitBatch()`
|
|
- extend `PluginToolbarDom` with `batchSubmitButton: HTMLButtonElement`
|
|
- create a new button:
|
|
|
|
```ts
|
|
const batchSubmitButton = document.createElement("button");
|
|
batchSubmitButton.type = "button";
|
|
batchSubmitButton.dataset.pluginBatchSubmit = "button";
|
|
batchSubmitButton.textContent = "提交批次";
|
|
```
|
|
|
|
- append it next to `exportButton`
|
|
- wire its click handler to `handlers.onSubmitBatch()`
|
|
- include it in `readToolbarDom()` and `setToolbarBusyState()`
|
|
|
|
- [ ] **Step 4: Re-run the focused market toolbar tests to verify green**
|
|
|
|
Run: `npm test -- tests/market-content-entry.test.ts`
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Commit the toolbar slice**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add src/content/market/plugin-toolbar.ts tests/market-content-entry.test.ts
|
|
git commit -m "feat: add batch submit toolbar action"
|
|
```
|
|
|
|
## Task 4: Wire Batch Submission Into The Market Controller
|
|
|
|
**Files:**
|
|
- Modify: `src/content/market/index.ts`
|
|
- Modify: `src/shared/auth-messages.ts`
|
|
- Modify: `src/background/auth/state.ts`
|
|
- Modify: `tests/market-content-entry.test.ts`
|
|
|
|
- [ ] **Step 1: Write the failing controller tests**
|
|
|
|
Add tests to `tests/market-content-entry.test.ts` for:
|
|
|
|
```ts
|
|
test("prompts for a batch name before submitting the current range", async () => {
|
|
document.body.innerHTML = buildMarketFixture();
|
|
const prompt = vi.fn(() => "618达人筛选第一批");
|
|
const submitBatch = vi.fn(async () => ({ ok: true }));
|
|
|
|
const { createMarketController } = await import("../src/content/market/index");
|
|
const controller = createMarketController({
|
|
document,
|
|
getAuthState: async () => ({
|
|
isAuthenticated: true,
|
|
resource: "https://talent-search.intelligrow.cn",
|
|
userInfo: { name: "王少卿", sub: "p7pdhhtde8kj" }
|
|
}),
|
|
loadAuthorMetrics: async () => ({
|
|
success: true,
|
|
rates: {
|
|
personalVideoAfterSearchRate: "0.01% - 0.1%",
|
|
singleVideoAfterSearchRate: "0.01% - 0.1%"
|
|
}
|
|
}),
|
|
promptBatchName: prompt,
|
|
submitBatch,
|
|
window
|
|
});
|
|
|
|
await controller.ready;
|
|
(document.querySelector('[data-plugin-batch-submit="button"]') as HTMLButtonElement).click();
|
|
await Promise.resolve();
|
|
|
|
expect(prompt).toHaveBeenCalled();
|
|
expect(submitBatch).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
batchName: "618达人筛选第一批",
|
|
logtoUserId: "p7pdhhtde8kj"
|
|
})
|
|
);
|
|
});
|
|
|
|
test("shows an error when the batch name is blank", async () => {
|
|
// same setup, but prompt returns " "
|
|
// expect toolbar status to contain "请输入批次名称"
|
|
});
|
|
|
|
test("does nothing when the prompt is cancelled", async () => {
|
|
// prompt returns null
|
|
// submitBatch is not called
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the focused market controller tests to verify red**
|
|
|
|
Run: `npm test -- tests/market-content-entry.test.ts`
|
|
|
|
Expected: FAIL because batch submission wiring does not exist yet
|
|
|
|
- [ ] **Step 3: Add the minimal market controller wiring**
|
|
|
|
Update `src/content/market/index.ts`:
|
|
|
|
- add optional dependencies:
|
|
|
|
```ts
|
|
getAuthState?: () => Promise<AuthStateValue>;
|
|
promptBatchName?: () => string | null;
|
|
submitBatch?: (payload: BatchPayload) => Promise<unknown>;
|
|
```
|
|
|
|
- default `getAuthState` to a `chrome.runtime.sendMessage({ type: "auth:get-state" })` wrapper
|
|
- default `promptBatchName` to `() => window.prompt("请输入批次名称")`
|
|
- default `submitBatch` to `createBatchSubmitClient(...).submitBatch`
|
|
- add `onSubmitBatch` handler in `ensurePluginToolbar(...)`
|
|
- behavior:
|
|
- read target via `readToolbarExportTarget()`
|
|
- prompt for batch name
|
|
- cancel on `null`
|
|
- reject blank names with `setToolbarExportStatus(toolbar, "请输入批次名称")`
|
|
- collect records via existing `exportRecords(target)`
|
|
- read auth state
|
|
- build payload with `createBatchPayload(...)`
|
|
- call `submitBatch(payload)`
|
|
- show `批次提交成功` on success
|
|
- show thrown message or `批次提交失败,请稍后重试` on failure
|
|
|
|
- [ ] **Step 4: Extend auth state only if the tests expose a gap**
|
|
|
|
If tests reveal missing fields, ensure `src/background/auth/state.ts` continues exposing:
|
|
- `userInfo.sub`
|
|
- `userInfo.name`
|
|
- `resource`
|
|
|
|
If `src/shared/auth-messages.ts` needs typing support, update only types, not message kinds.
|
|
|
|
- [ ] **Step 5: Re-run the focused market controller tests to verify green**
|
|
|
|
Run: `npm test -- tests/market-content-entry.test.ts`
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 6: Commit the market controller slice**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add src/content/market/index.ts src/shared/auth-messages.ts src/background/auth/state.ts tests/market-content-entry.test.ts
|
|
git commit -m "feat: wire market batch submission flow"
|
|
```
|
|
|
|
## Task 5: Extend The Mock Server And Popup Debugging
|
|
|
|
**Files:**
|
|
- Modify: `scripts/mock-protected-api.mjs`
|
|
- Modify: `tests/mock-protected-api.test.ts`
|
|
- Modify: `src/popup/view.ts`
|
|
- Modify: `tests/popup-entry.test.ts` only if needed for debug output wording
|
|
- Modify: `README.md`
|
|
|
|
- [ ] **Step 1: Write the failing mock batch endpoint tests**
|
|
|
|
Add cases to `tests/mock-protected-api.test.ts`:
|
|
|
|
```ts
|
|
test("accepts a batch payload when a Bearer token is present", async () => {
|
|
const server = createMockProtectedApiServer({ port: 0 });
|
|
await server.start();
|
|
|
|
const response = await fetch(`${server.baseUrl}/api/mock/batches`, {
|
|
body: JSON.stringify({
|
|
authors: [{ authorId: "111", authorName: "达人A" }],
|
|
batchId: "批次A-2026-04-22T12:30:00.000Z",
|
|
batchName: "批次A",
|
|
createdAt: "2026-04-22T12:30:00.000Z",
|
|
creatorName: "王少卿",
|
|
logtoUserId: "p7pdhhtde8kj",
|
|
resource: "https://talent-search.intelligrow.cn"
|
|
}),
|
|
headers: {
|
|
Authorization: "Bearer abc123",
|
|
"Content-Type": "application/json"
|
|
},
|
|
method: "POST"
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
await expect(response.json()).resolves.toEqual(
|
|
expect.objectContaining({
|
|
acceptedCount: 1,
|
|
ok: true,
|
|
source: "mock-batch-submit"
|
|
})
|
|
);
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the mock server tests to verify red**
|
|
|
|
Run: `npm test -- tests/mock-protected-api.test.ts`
|
|
|
|
Expected: FAIL because the batch endpoint does not exist yet
|
|
|
|
- [ ] **Step 3: Implement the minimal mock batch endpoint**
|
|
|
|
Update `scripts/mock-protected-api.mjs`:
|
|
|
|
- keep the existing `/api/mock/protected`
|
|
- add `/api/mock/batches` for `POST`
|
|
- require the same Bearer header validation
|
|
- read the JSON body and return:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"source": "mock-batch-submit",
|
|
"acceptedCount": 1,
|
|
"batchId": "<echoed batch id>"
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Update the docs**
|
|
|
|
Add to `README.md`:
|
|
- how to trigger `提交批次`
|
|
- how the prompt should be filled
|
|
- expected mock response
|
|
|
|
- [ ] **Step 5: Re-run the mock server tests to verify green**
|
|
|
|
Run: `npm test -- tests/mock-protected-api.test.ts`
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 6: Commit the mock batch slice**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add scripts/mock-protected-api.mjs tests/mock-protected-api.test.ts README.md
|
|
git commit -m "feat: add mock batch submit endpoint"
|
|
```
|
|
|
|
## Final Verification
|
|
|
|
- [ ] Run the focused batch feature tests:
|
|
|
|
```bash
|
|
npm test -- tests/auth-config.test.ts tests/batch-payload.test.ts tests/batch-submit-client.test.ts tests/mock-protected-api.test.ts tests/market-content-entry.test.ts
|
|
```
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] Run the full test suite:
|
|
|
|
```bash
|
|
npm test
|
|
```
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] Run the production build:
|
|
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
Expected: build completes and updates `dist/`
|
|
|
|
- [ ] Perform the manual smoke test:
|
|
|
|
1. Start `npm run mock:protected-api`
|
|
2. Reload the unpacked extension from `dist/`
|
|
3. Log in through the popup
|
|
4. Open the Xingtu market page
|
|
5. Choose an export range
|
|
6. Click `提交批次`
|
|
7. Enter a batch name in `prompt()`
|
|
8. Confirm success text appears
|
|
9. Verify the mock response contains the batch id and accepted creator count
|