Files
openclaw/extensions/line/src/send.location.test.ts
Eden 14b59a06f5 fix(line): deliver a location LINE cannot render instead of dropping it (#126298)
* fix(line): deliver a location LINE cannot render instead of dropping it

A location whose title or address is blank makes LINE reject the whole
request, so every delivery path filtered it out before sending. The reply
then arrived without the pin, and nothing told the user or the operator that
a part of the message had been removed: the coordinates the sender supplied
were discarded silently.

The location builder now answers with the values the sender wrote — the
non-blank label plus the coordinates — as a text message, so an unrenderable
pin degrades into something the chat can show instead of disappearing. Both
delivery paths inherit that from the single builder, and the three call sites
that used to skip a null result no longer have a branch to take.

Live validation against the Messaging API confirms the shape: the authored
location is rejected with "May not be empty" on messages[0].address, the same
rejection kills an entire batch that also carries valid text, and both
degraded forms validate cleanly.

* fix(line): bound the location fallback to LINE's text limit

Nothing caps the location labels at the schema, so a long title with a blank
address produced one raw text message past LINE's 5,000-character limit — the
provider rejected it and the location was lost exactly as silently as before.

The pin path already caps each label at 100 characters. Name that limit and
apply it in the fallback too, so both forms carry the same bound instead of
one of them re-deriving the provider's rules.

* test(line): merge the duplicate send.js imports
2026-08-20 00:29:51 +08:00

69 lines
2.2 KiB
TypeScript

// Line tests cover how an unrenderable location reaches the chat.
import { describe, expect, it } from "vitest";
import { createLocationMessage } from "./send.js";
describe("createLocationMessage", () => {
it("keeps a renderable location as a pin", () => {
const message = createLocationMessage({
title: "Blue Bottle",
address: "1 Main Street",
latitude: 35.6895,
longitude: 139.6917,
});
expect(message).toEqual({
type: "location",
title: "Blue Bottle",
address: "1 Main Street",
latitude: 35.6895,
longitude: 139.6917,
});
});
it.each([
{ name: "title", title: " ", address: "1 Main Street", kept: "1 Main Street" },
{ name: "address", title: "Blue Bottle", address: " ", kept: "Blue Bottle" },
])(
"delivers a blank-$name location as the values the sender wrote",
({ title, address, kept }) => {
// LINE rejects the pin, but the sender's label and coordinates are still
// deliverable, so they must reach the chat instead of being dropped.
const message = createLocationMessage({
title,
address,
latitude: 35.6895,
longitude: 139.6917,
});
expect(message).toEqual({ type: "text", text: `${kept}\n35.6895, 139.6917` });
},
);
it("caps an oversized label so the fallback stays inside LINE's text limit", () => {
// Nothing bounds the label at the schema, so an unbounded fallback would be
// rejected for length and lose the location exactly as before.
const message = createLocationMessage({
title: "A".repeat(6000),
address: " ",
latitude: 35.6895,
longitude: 139.6917,
});
expect(message.type).toBe("text");
const text = (message as { text: string }).text;
expect(text).toBe("A".repeat(100) + String.fromCharCode(10) + "35.6895, 139.6917");
expect(text.length).toBeLessThanOrEqual(5000);
});
it("still delivers the coordinates when the sender left both labels blank", () => {
const message = createLocationMessage({
title: " ",
address: "",
latitude: 35.6895,
longitude: 139.6917,
});
expect(message).toEqual({ type: "text", text: "35.6895, 139.6917" });
});
});