

























@@ -70,6 +70,75 @@ type UploadTestClient = WebClient & {
7070};
7171};
727273+type MockCalls = {
74+mock: { calls: unknown[][] };
75+};
76+77+function isRecord(value: unknown): value is Record<string, unknown> {
78+return typeof value === "object" && value !== null;
79+}
80+81+function requireRecord(value: unknown, label: string): Record<string, unknown> {
82+expect(isRecord(value), `${label} should be an object`).toBe(true);
83+if (!isRecord(value)) {
84+throw new Error(`${label} should be an object`);
85+}
86+return value;
87+}
88+89+function requireArray(value: unknown, label: string): unknown[] {
90+expect(Array.isArray(value), `${label} should be an array`).toBe(true);
91+if (!Array.isArray(value)) {
92+throw new Error(`${label} should be an array`);
93+}
94+return value;
95+}
96+97+function expectFields(record: Record<string, unknown>, expected: Record<string, unknown>) {
98+for (const [key, value] of Object.entries(expected)) {
99+expect(record[key], key).toEqual(value);
100+}
101+}
102+103+function expectCallFirstArg(
104+mock: MockCalls,
105+callNumber: number,
106+expected: Record<string, unknown>,
107+label = "mock first argument",
108+): Record<string, unknown> {
109+expect(mock.mock.calls.length).toBeGreaterThanOrEqual(callNumber);
110+const [firstArg] = mock.mock.calls[callNumber - 1] ?? [];
111+const record = requireRecord(firstArg, label);
112+expectFields(record, expected);
113+return record;
114+}
115+116+function expectOnlyCallFirstArg(
117+mock: MockCalls,
118+expected: Record<string, unknown>,
119+label?: string,
120+): Record<string, unknown> {
121+expect(mock.mock.calls).toHaveLength(1);
122+return expectCallFirstArg(mock, 1, expected, label);
123+}
124+125+function expectCompletedUpload(params: {
126+client: UploadTestClient;
127+expected: Record<string, unknown>;
128+file?: Record<string, unknown>;
129+}) {
130+const payload = expectOnlyCallFirstArg(
131+params.client.files.completeUploadExternal,
132+params.expected,
133+"complete upload payload",
134+);
135+if (params.file) {
136+const [file] = requireArray(payload.files, "complete upload files");
137+expectFields(requireRecord(file, "complete upload file"), params.file);
138+}
139+return payload;
140+}
141+73142function createUploadTestClient(): UploadTestClient {
74143return {
75144conversations: {
@@ -124,12 +193,11 @@ describe("sendMessageSlack file upload with user IDs", () => {
124193users: "U2ZH3MFSR",
125194});
126195127-expect(client.files.completeUploadExternal).toHaveBeenCalledWith(
128-expect.objectContaining({
129-channel_id: "D99RESOLVED",
130-files: [expect.objectContaining({ id: "F001", title: "screenshot.png" })],
131-}),
132-);
196+expectCompletedUpload({
197+ client,
198+expected: { channel_id: "D99RESOLVED" },
199+file: { id: "F001", title: "screenshot.png" },
200+});
133201});
134202135203it("resolves prefixed user ID to DM channel before completing upload", async () => {
@@ -145,9 +213,7 @@ describe("sendMessageSlack file upload with user IDs", () => {
145213expect(client.conversations.open).toHaveBeenCalledWith({
146214users: "UABC123",
147215});
148-expect(client.files.completeUploadExternal).toHaveBeenCalledWith(
149-expect.objectContaining({ channel_id: "D99RESOLVED" }),
150-);
216+expectCompletedUpload({ client, expected: { channel_id: "D99RESOLVED" } });
151217});
152218153219it("posts text-only user-target DMs directly without conversations.open", async () => {
@@ -167,13 +233,10 @@ describe("sendMessageSlack file upload with user IDs", () => {
167233168234expect(client.conversations.open).not.toHaveBeenCalled();
169235expect(client.chat.postMessage).toHaveBeenCalledTimes(2);
170-expect(client.chat.postMessage).toHaveBeenNthCalledWith(
171-2,
172-expect.objectContaining({
173-channel: "UABC123",
174-text: "second",
175-}),
176-);
236+expectCallFirstArg(client.chat.postMessage, 2, {
237+channel: "UABC123",
238+text: "second",
239+});
177240});
178241179242it("serializes concurrent sends to the same Slack target", async () => {
@@ -209,26 +272,25 @@ describe("sendMessageSlack file upload with user IDs", () => {
209272}
210273resolveFirst();
211274212-await expect(first).resolves.toMatchObject({
275+const firstResult = await first;
276+expectFields(requireRecord(firstResult, "first send result"), {
213277channelId: "C123CHAN",
214278messageId: "1.000",
215-receipt: expect.objectContaining({
216-primaryPlatformMessageId: "1.000",
217-platformMessageIds: ["1.000"],
218-}),
219279});
220-await expect(second).resolves.toMatchObject({
280+expectFields(requireRecord(firstResult.receipt, "first receipt"), {
281+primaryPlatformMessageId: "1.000",
282+platformMessageIds: ["1.000"],
283+});
284+const secondResult = await second;
285+expectFields(requireRecord(secondResult, "second send result"), {
221286channelId: "C123CHAN",
222287messageId: "2.000",
223-receipt: expect.objectContaining({
224-primaryPlatformMessageId: "2.000",
225-platformMessageIds: ["2.000"],
226-}),
227-});
228-expect(client.chat.postMessage).toHaveBeenNthCalledWith(
229-2,
230-expect.objectContaining({ text: "second" }),
231-);
288+});
289+expectFields(requireRecord(secondResult.receipt, "second receipt"), {
290+primaryPlatformMessageId: "2.000",
291+platformMessageIds: ["2.000"],
292+});
293+expectCallFirstArg(client.chat.postMessage, 2, { text: "second" });
232294});
233295234296it("scopes DM channel resolution cache by token identity", async () => {
@@ -261,19 +323,20 @@ describe("sendMessageSlack file upload with user IDs", () => {
261323});
262324263325expect(client.conversations.open).not.toHaveBeenCalled();
264-expect(client.files.completeUploadExternal).toHaveBeenCalledWith(
265-expect.objectContaining({ channel_id: "C123CHAN" }),
266-);
267-expect(result.receipt).toMatchObject({
326+expectCompletedUpload({ client, expected: { channel_id: "C123CHAN" } });
327+expectFields(requireRecord(result.receipt, "receipt"), {
268328primaryPlatformMessageId: "F001",
269329platformMessageIds: ["F001"],
270-parts: [
271-expect.objectContaining({
272-platformMessageId: "F001",
273-kind: "media",
274-raw: expect.objectContaining({ channel: "slack", channelId: "C123CHAN" }),
275-}),
276-],
330+});
331+const [part] = requireArray(result.receipt.parts, "receipt parts");
332+const partRecord = requireRecord(part, "receipt part");
333+expectFields(partRecord, {
334+platformMessageId: "F001",
335+kind: "media",
336+});
337+expectFields(requireRecord(partRecord.raw, "receipt raw"), {
338+channel: "slack",
339+channelId: "C123CHAN",
277340});
278341});
279342@@ -290,9 +353,7 @@ describe("sendMessageSlack file upload with user IDs", () => {
290353expect(client.conversations.open).toHaveBeenCalledWith({
291354users: "U777TEST",
292355});
293-expect(client.files.completeUploadExternal).toHaveBeenCalledWith(
294-expect.objectContaining({ channel_id: "D99RESOLVED" }),
295-);
356+expectCompletedUpload({ client, expected: { channel_id: "D99RESOLVED" } });
296357});
297358298359it("uploads bytes to the presigned URL and completes with thread+caption", async () => {
@@ -310,26 +371,24 @@ describe("sendMessageSlack file upload with user IDs", () => {
310371filename: "screenshot.png",
311372length: Buffer.from("fake-image").length,
312373});
313-expect(globalThis.fetch).toHaveBeenCalledWith(
314-"https://uploads.slack.test/upload",
315-expect.objectContaining({
316-method: "POST",
317-}),
318-);
319-expect(fetchWithSsrFGuard).toHaveBeenCalledWith(
320-expect.objectContaining({
321-url: "https://uploads.slack.test/upload",
322-mode: "trusted_env_proxy",
323-auditContext: "slack-upload-file",
324-}),
325-);
326-expect(client.files.completeUploadExternal).toHaveBeenCalledWith(
327-expect.objectContaining({
374+const fetchCalls = (globalThis.fetch as unknown as MockCalls).mock.calls;
375+expect(fetchCalls).toHaveLength(1);
376+const [fetchUrl, fetchInit] = fetchCalls[0] ?? [];
377+expect(fetchUrl).toBe("https://uploads.slack.test/upload");
378+expectFields(requireRecord(fetchInit, "fetch init"), { method: "POST" });
379+expectOnlyCallFirstArg(fetchWithSsrFGuard, {
380+url: "https://uploads.slack.test/upload",
381+mode: "trusted_env_proxy",
382+auditContext: "slack-upload-file",
383+});
384+expectCompletedUpload({
385+ client,
386+expected: {
328387channel_id: "C123CHAN",
329388initial_comment: "caption",
330389thread_ts: "171.222",
331-}),
332-);
390+},
391+});
333392expect(hasSlackThreadParticipation("default", "C123CHAN", "171.222")).toBe(true);
334393expect(result.receipt.threadId).toBe("171.222");
335394});
@@ -350,11 +409,11 @@ describe("sendMessageSlack file upload with user IDs", () => {
350409filename: "custom-name.bin",
351410length: Buffer.from("fake-image").length,
352411});
353-expect(client.files.completeUploadExternal).toHaveBeenCalledWith(
354-expect.objectContaining({
355- files: [expect.objectContaining({ id: "F001", title: "Custom Title" })],
356-}),
357-);
412+expectCompletedUpload({
413+client,
414+expected: {},
415+file: { id: "F001", title: "Custom Title" },
416+});
358417});
359418360419it("uses uploadFileName as the title fallback when uploadTitle is omitted", async () => {
@@ -372,10 +431,10 @@ describe("sendMessageSlack file upload with user IDs", () => {
372431filename: "custom-name.bin",
373432length: Buffer.from("fake-image").length,
374433});
375-expect(client.files.completeUploadExternal).toHaveBeenCalledWith(
376-expect.objectContaining({
377- files: [expect.objectContaining({ id: "F001", title: "custom-name.bin" })],
378-}),
379-);
434+expectCompletedUpload({
435+client,
436+expected: {},
437+file: { id: "F001", title: "custom-name.bin" },
438+});
380439});
381440});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。