feat: add mock batch submit endpoint

This commit is contained in:
admin123 2026-04-22 13:59:34 +08:00
parent 766c6a624f
commit b1bb28f5aa
3 changed files with 111 additions and 21 deletions

View File

@ -56,6 +56,18 @@ The popup dev panel is controlled by `enableDevAuthPanel`.
6. Click `测试受保护接口` 6. Click `测试受保护接口`
7. Confirm the popup shows JSON containing `"source": "mock-protected-api"` and `"message": "authorized"` 7. Confirm the popup shows JSON containing `"source": "mock-protected-api"` and `"message": "authorized"`
## Batch Submit Mock Test
1. Run `npm run mock:protected-api`
2. Run `npm run build`
3. Reload the unpacked extension from `dist/`
4. Open `https://xingtu.cn/ad/creator/market`
5. Choose an export range in the plugin toolbar
6. Click `提交批次`
7. Enter a batch name in the browser prompt
8. Confirm the toolbar shows `批次提交成功`
9. Confirm the mock batch response accepts the payload and reports the submitted `batchId`
## Market Auth Gate ## Market Auth Gate
When the market page is opened without a valid auth state, the content script renders When the market page is opened without a valid auth state, the content script renders

View File

@ -12,34 +12,49 @@ export function createMockProtectedApiServer({ port = 4319 } = {}) {
return `http://127.0.0.1:${resolvedPort}`; return `http://127.0.0.1:${resolvedPort}`;
}, },
async start() { async start() {
server = http.createServer((request, response) => { server = http.createServer(async (request, response) => {
if (request.url !== "/api/mock/protected") { if (request.url === "/api/mock/protected") {
response.writeHead(404, { "content-type": "application/json" }); const authHeader = readBearerToken(request, response);
response.end(JSON.stringify({ ok: false, error: "not-found" })); if (!authHeader) {
return;
}
response.writeHead(200, { "content-type": "application/json" });
response.end(
JSON.stringify({
ok: true,
source: "mock-protected-api",
message: "authorized",
receivedAuthHeader: authHeader
})
);
return; return;
} }
const authHeader = request.headers.authorization ?? ""; if (request.url === "/api/mock/batches" && request.method === "POST") {
const isBearer = const authHeader = readBearerToken(request, response);
typeof authHeader === "string" && if (!authHeader) {
authHeader.startsWith("Bearer ") && return;
authHeader.length > "Bearer ".length; }
if (!isBearer) { const payload = await readJsonBody(request);
response.writeHead(401, { "content-type": "application/json" }); const authors = Array.isArray(payload?.authors) ? payload.authors : [];
response.end(JSON.stringify({ ok: false, error: "unauthorized" })); response.writeHead(200, { "content-type": "application/json" });
response.end(
JSON.stringify({
ok: true,
source: "mock-batch-submit",
acceptedCount: authors.length,
batchId:
typeof payload?.batchId === "string" ? payload.batchId : null,
receivedAuthHeader: authHeader
})
);
return; return;
} }
response.writeHead(200, { "content-type": "application/json" }); response.writeHead(404, { "content-type": "application/json" });
response.end( response.end(JSON.stringify({ ok: false, error: "not-found" }));
JSON.stringify({
ok: true,
source: "mock-protected-api",
message: "authorized",
receivedAuthHeader: authHeader
})
);
}); });
await new Promise((resolve) => { await new Promise((resolve) => {
@ -65,6 +80,36 @@ export function createMockProtectedApiServer({ port = 4319 } = {}) {
}; };
} }
function readBearerToken(request, response) {
const authHeader = request.headers.authorization ?? "";
const isBearer =
typeof authHeader === "string" &&
authHeader.startsWith("Bearer ") &&
authHeader.length > "Bearer ".length;
if (!isBearer) {
response.writeHead(401, { "content-type": "application/json" });
response.end(JSON.stringify({ ok: false, error: "unauthorized" }));
return null;
}
return authHeader;
}
async function readJsonBody(request) {
const chunks = [];
for await (const chunk of request) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
if (chunks.length === 0) {
return null;
}
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
}
if (import.meta.url === `file://${process.argv[1]}`) { if (import.meta.url === `file://${process.argv[1]}`) {
const server = createMockProtectedApiServer(); const server = createMockProtectedApiServer();
await server.start(); await server.start();

View File

@ -44,4 +44,37 @@ describe("mock-protected-api", () => {
error: "unauthorized" error: "unauthorized"
}); });
}); });
test("accepts a batch payload when a Bearer token is present", async () => {
const server = createMockProtectedApiServer({ port: 0 });
await server.start();
servers.push(server);
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,
batchId: "批次A-2026-04-22T12:30:00.000Z",
ok: true,
source: "mock-batch-submit"
})
);
});
}); });