fix(nextcloud-talk): centralize integer coercion

This commit is contained in:
Peter Steinberger
2026-05-29 06:45:24 -04:00
parent 4a206db106
commit ed59629ccd
4 changed files with 47 additions and 10 deletions

View File

@@ -80,6 +80,19 @@ describe("probeNextcloudTalkBotResponseFeature", () => {
});
});
it("normalizes signed decimal bot feature strings through the shared parser", async () => {
mockBotAdmin("+011");
await expect(probeNextcloudTalkBotResponseFeature({ account: account() })).resolves.toEqual({
ok: true,
code: "ok",
botId: "7",
botName: "OpenClaw",
features: 11,
message: 'Nextcloud Talk bot "OpenClaw" has the response feature.',
});
});
it("reports missing response feature for the matching webhook bot", async () => {
mockBotAdmin(1 | 8);

View File

@@ -1,4 +1,5 @@
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
import { fetchWithSsrFGuard } from "../runtime-api.js";
import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
@@ -50,11 +51,7 @@ function coerceFeatureMask(value: unknown): number | undefined {
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
return value;
}
if (typeof value === "string" && /^[+-]?\d+$/.test(value.trim())) {
const parsed = Number(value.trim());
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : undefined;
}
return undefined;
return parseStrictNonNegativeInteger(value);
}
function formatMissingResponseFeatureMessage(bot: NextcloudTalkBotAdminEntry, features?: number) {

View File

@@ -73,6 +73,36 @@ describe("nextcloud talk room info", () => {
expect(release).toHaveBeenCalledTimes(1);
});
it("normalizes signed decimal room type strings through the shared parser", async () => {
fetchWithSsrFGuard.mockResolvedValue({
response: {
ok: true,
json: async () => ({
ocs: {
data: {
type: "+01",
},
},
}),
},
release: vi.fn(async () => {}),
});
await expect(
resolveNextcloudTalkRoomKind({
account: {
accountId: "acct-direct-string",
baseUrl: "https://nc.example.com",
config: {
apiUser: "bot",
apiPassword: "secret",
},
} as never,
roomToken: "room-direct-string",
}),
).resolves.toBe("direct");
});
it("does not coerce partial room type strings", async () => {
fetchWithSsrFGuard.mockResolvedValue({
response: {

View File

@@ -1,4 +1,5 @@
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
import { ssrfPolicyFromPrivateNetworkOptIn } from "openclaw/plugin-sdk/ssrf-runtime";
import { fetchWithSsrFGuard, type RuntimeEnv } from "../runtime-api.js";
@@ -27,11 +28,7 @@ function coerceRoomType(value: unknown): number | undefined {
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) {
return value;
}
if (typeof value === "string" && /^[+-]?\d+$/.test(value.trim())) {
const parsed = Number(value.trim());
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
}
return undefined;
return parseStrictPositiveInteger(value);
}
function resolveRoomKindFromType(type: number | undefined): "direct" | "group" | undefined {