From a782b73a7253c6e95fa1af76087bfc26eaf303ef Mon Sep 17 00:00:00 2001 From: Yuta Kasai Date: Sat, 20 Jun 2026 20:34:30 +0900 Subject: [PATCH 1/3] Add failing tests for undefined multipart form fields --- test/libs-manageAudience.spec.ts | 72 +++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/test/libs-manageAudience.spec.ts b/test/libs-manageAudience.spec.ts index 29046230e..e13193b61 100644 --- a/test/libs-manageAudience.spec.ts +++ b/test/libs-manageAudience.spec.ts @@ -1,7 +1,7 @@ import { manageAudience } from "../lib/index.js"; import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; -import { deepEqual, equal, match } from "node:assert"; +import { deepEqual, equal, match, ok, strictEqual } from "node:assert"; import { describe, it, beforeAll, afterAll, afterEach } from "vitest"; @@ -129,4 +129,74 @@ describe("manageAudience", () => { equal(requestCount, 1); deepEqual(res.body, {}); }); + + it("createAudienceForUploadingUserIds omits undefined optional fields", async () => { + let received: FormData | null = null; + server.use( + http.post( + "https://api-data.line.me/v2/bot/audienceGroup/upload/byFile", + async ({ request }) => { + received = await request.formData(); + return HttpResponse.json({}); + }, + ), + ); + + const file = new Blob(["user-id-1"], { type: "text/plain" }); + await blobClient.createAudienceForUploadingUserIds(file); + + const form: FormData = received!; + ok(form); + ok(form.get("file") instanceof File); + strictEqual(form.has("description"), false); + strictEqual(form.has("isIfaAudience"), false); + strictEqual(form.has("uploadDescription"), false); + }); + + it("addUserIdsToAudience omits undefined optional fields", async () => { + let received: FormData | null = null; + server.use( + http.put( + "https://api-data.line.me/v2/bot/audienceGroup/upload/byFile", + async ({ request }) => { + received = await request.formData(); + return HttpResponse.json({}); + }, + ), + ); + + const file = new Blob(["user-id-1"], { type: "text/plain" }); + await blobClient.addUserIdsToAudience(file); + + const form: FormData = received!; + ok(form); + ok(form.get("file") instanceof File); + strictEqual(form.has("audienceGroupId"), false); + strictEqual(form.has("uploadDescription"), false); + }); + + it("createAudienceForUploadingUserIds preserves false for isIfaAudience", async () => { + let received: FormData | null = null; + server.use( + http.post( + "https://api-data.line.me/v2/bot/audienceGroup/upload/byFile", + async ({ request }) => { + received = await request.formData(); + return HttpResponse.json({}); + }, + ), + ); + + const file = new Blob(["user-id-1"], { type: "text/plain" }); + await blobClient.createAudienceForUploadingUserIds( + file, + "test_description", + false, + ); + + const form: FormData = received!; + ok(form); + strictEqual(form.get("isIfaAudience"), "false"); + strictEqual(form.get("description"), "test_description"); + }); }); From ce5613d227d20e9c361031a27a96bd08d1b841c7 Mon Sep 17 00:00:00 2001 From: Yuta Kasai Date: Sat, 20 Jun 2026 20:34:30 +0900 Subject: [PATCH 2/3] Omit undefined multipart form fields --- .../apiBody/multipart.pebble | 16 +++++++++---- .../api/manageAudienceBlobClient.ts | 24 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/generator/src/main/resources/line-bot-sdk-nodejs-generator/apiBody/multipart.pebble b/generator/src/main/resources/line-bot-sdk-nodejs-generator/apiBody/multipart.pebble index a36b0251d..5f1791140 100644 --- a/generator/src/main/resources/line-bot-sdk-nodejs-generator/apiBody/multipart.pebble +++ b/generator/src/main/resources/line-bot-sdk-nodejs-generator/apiBody/multipart.pebble @@ -8,11 +8,17 @@ {% endif -%} const form = new FormData(); {% for param in op.formParams -%} - {% if param.isFile -%} - form.append("{{param.paramName}}", {{param.paramName}}); // file - {% else -%} - form.append("{{param.paramName}}", String({{param.paramName}})); - {% endif -%} +{% if not param.required -%} + if ({{ param.paramName }} !== undefined) { +{% endif -%} +{% if param.isFile -%} + form.append("{{ param.baseName }}", {{ param.paramName }}); +{% else -%} + form.append("{{ param.baseName }}", String({{ param.paramName }})); +{% endif -%} +{% if not param.required -%} + } +{% endif -%} {% endfor %} const res = await this.httpClient.{{op.httpMethod|lower}}{% if op.hasFormParams %}Form{% endif %}Multipart( {% if op.pathParams|length > 0 %}requestPath{% else %}"{{ op.path }}"{% endif %}, diff --git a/lib/manage-audience/api/manageAudienceBlobClient.ts b/lib/manage-audience/api/manageAudienceBlobClient.ts index 07723dd3a..f1f99338b 100644 --- a/lib/manage-audience/api/manageAudienceBlobClient.ts +++ b/lib/manage-audience/api/manageAudienceBlobClient.ts @@ -87,9 +87,13 @@ export class ManageAudienceBlobClient { uploadDescription?: string, ): Promise> { const form = new FormData(); - form.append("audienceGroupId", String(audienceGroupId)); - form.append("uploadDescription", String(uploadDescription)); - form.append("file", file); // file + if (audienceGroupId !== undefined) { + form.append("audienceGroupId", String(audienceGroupId)); + } + if (uploadDescription !== undefined) { + form.append("uploadDescription", String(uploadDescription)); + } + form.append("file", file); const res = await this.httpClient.putFormMultipart( "/v2/bot/audienceGroup/upload/byFile", @@ -141,10 +145,16 @@ export class ManageAudienceBlobClient { uploadDescription?: string, ): Promise> { const form = new FormData(); - form.append("description", String(description)); - form.append("isIfaAudience", String(isIfaAudience)); - form.append("uploadDescription", String(uploadDescription)); - form.append("file", file); // file + if (description !== undefined) { + form.append("description", String(description)); + } + if (isIfaAudience !== undefined) { + form.append("isIfaAudience", String(isIfaAudience)); + } + if (uploadDescription !== undefined) { + form.append("uploadDescription", String(uploadDescription)); + } + form.append("file", file); const res = await this.httpClient.postFormMultipart( "/v2/bot/audienceGroup/upload/byFile", From 5184ac0b0fb1324d83e12c16b62d731d8d6943b1 Mon Sep 17 00:00:00 2001 From: Yuta Kasai Date: Mon, 10 Aug 2026 23:22:40 +0900 Subject: [PATCH 3/3] keep comment --- .../line-bot-sdk-nodejs-generator/apiBody/multipart.pebble | 2 +- lib/manage-audience/api/manageAudienceBlobClient.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/generator/src/main/resources/line-bot-sdk-nodejs-generator/apiBody/multipart.pebble b/generator/src/main/resources/line-bot-sdk-nodejs-generator/apiBody/multipart.pebble index 5f1791140..f1bfe6c7a 100644 --- a/generator/src/main/resources/line-bot-sdk-nodejs-generator/apiBody/multipart.pebble +++ b/generator/src/main/resources/line-bot-sdk-nodejs-generator/apiBody/multipart.pebble @@ -12,7 +12,7 @@ if ({{ param.paramName }} !== undefined) { {% endif -%} {% if param.isFile -%} - form.append("{{ param.baseName }}", {{ param.paramName }}); + form.append("{{ param.baseName }}", {{ param.paramName }}); // file {% else -%} form.append("{{ param.baseName }}", String({{ param.paramName }})); {% endif -%} diff --git a/lib/manage-audience/api/manageAudienceBlobClient.ts b/lib/manage-audience/api/manageAudienceBlobClient.ts index fd55fe0c2..20db329db 100644 --- a/lib/manage-audience/api/manageAudienceBlobClient.ts +++ b/lib/manage-audience/api/manageAudienceBlobClient.ts @@ -120,7 +120,7 @@ export class ManageAudienceBlobClient { if (uploadDescription !== undefined) { form.append("uploadDescription", String(uploadDescription)); } - form.append("file", file); + form.append("file", file); // file const res = await this.httpClient.putFormMultipart( "/v2/bot/audienceGroup/upload/byFile", @@ -184,7 +184,7 @@ export class ManageAudienceBlobClient { if (uploadDescription !== undefined) { form.append("uploadDescription", String(uploadDescription)); } - form.append("file", file); + form.append("file", file); // file const res = await this.httpClient.postFormMultipart( "/v2/bot/audienceGroup/upload/byFile",