@openclaw/nextcloud-talk 2026.3.12 → 2026.5.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api.ts +1 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +4 -0
- package/doctor-contract-api.ts +1 -0
- package/index.ts +15 -12
- package/openclaw.plugin.json +804 -1
- package/package.json +31 -4
- package/runtime-api.ts +33 -0
- package/secret-contract-api.ts +5 -0
- package/setup-entry.ts +13 -0
- package/src/accounts.ts +25 -42
- package/src/approval-auth.test.ts +17 -0
- package/src/approval-auth.ts +27 -0
- package/src/channel-api.ts +5 -0
- package/src/channel.adapters.ts +52 -0
- package/src/channel.core.test.ts +75 -0
- package/src/channel.lifecycle.test.ts +81 -0
- package/src/channel.ts +157 -388
- package/src/config-schema.ts +27 -22
- package/src/core.test.ts +397 -0
- package/src/doctor-contract.ts +9 -0
- package/src/doctor.test.ts +40 -0
- package/src/doctor.ts +10 -0
- package/src/gateway.ts +109 -0
- package/src/inbound.authz.test.ts +87 -22
- package/src/inbound.behavior.test.ts +202 -0
- package/src/inbound.ts +28 -26
- package/src/monitor-runtime.ts +138 -0
- package/src/monitor.replay.test.ts +238 -0
- package/src/monitor.test-harness.ts +2 -2
- package/src/monitor.ts +125 -155
- package/src/normalize.ts +7 -2
- package/src/policy.ts +23 -31
- package/src/replay-guard.ts +74 -11
- package/src/room-info.test.ts +116 -0
- package/src/room-info.ts +11 -3
- package/src/runtime.ts +6 -3
- package/src/secret-contract.ts +103 -0
- package/src/secret-input.ts +1 -10
- package/src/send.cfg-threading.test.ts +153 -0
- package/src/send.runtime.ts +8 -0
- package/src/send.ts +126 -106
- package/src/session-route.ts +40 -0
- package/src/setup-core.ts +248 -0
- package/src/setup-surface.ts +190 -0
- package/src/setup.test.ts +422 -0
- package/src/signature.ts +20 -10
- package/src/types.ts +19 -16
- package/tsconfig.json +16 -0
- package/src/accounts.test.ts +0 -30
- package/src/channel.startup.test.ts +0 -83
- package/src/config-schema.test.ts +0 -36
- package/src/format.ts +0 -79
- package/src/monitor.auth-order.test.ts +0 -28
- package/src/monitor.backend.test.ts +0 -27
- package/src/monitor.read-body.test.ts +0 -16
- package/src/onboarding.ts +0 -302
- package/src/policy.test.ts +0 -138
- package/src/replay-guard.test.ts +0 -70
- package/src/send.test.ts +0 -104
package/src/types.ts
CHANGED
|
@@ -4,9 +4,7 @@ import type {
|
|
|
4
4
|
DmPolicy,
|
|
5
5
|
GroupPolicy,
|
|
6
6
|
SecretInput,
|
|
7
|
-
} from "
|
|
8
|
-
|
|
9
|
-
export type { DmPolicy, GroupPolicy };
|
|
7
|
+
} from "../runtime-api.js";
|
|
10
8
|
|
|
11
9
|
export type NextcloudTalkRoomConfig = {
|
|
12
10
|
requireMention?: boolean;
|
|
@@ -22,6 +20,11 @@ export type NextcloudTalkRoomConfig = {
|
|
|
22
20
|
systemPrompt?: string;
|
|
23
21
|
};
|
|
24
22
|
|
|
23
|
+
type NextcloudTalkNetworkConfig = {
|
|
24
|
+
/** Dangerous opt-in for self-hosted Nextcloud Talk on trusted private/internal hosts. */
|
|
25
|
+
dangerouslyAllowPrivateNetwork?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
25
28
|
export type NextcloudTalkAccountConfig = {
|
|
26
29
|
/** Optional display name for this account (used in CLI/UI lists). */
|
|
27
30
|
name?: string;
|
|
@@ -75,9 +78,11 @@ export type NextcloudTalkAccountConfig = {
|
|
|
75
78
|
responsePrefix?: string;
|
|
76
79
|
/** Media upload max size in MB. */
|
|
77
80
|
mediaMaxMb?: number;
|
|
81
|
+
/** Network policy overrides for self-hosted Nextcloud Talk on trusted private/internal hosts. */
|
|
82
|
+
network?: NextcloudTalkNetworkConfig;
|
|
78
83
|
};
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
type NextcloudTalkConfig = {
|
|
81
86
|
/** Optional per-account Nextcloud Talk configuration (multi-account). */
|
|
82
87
|
accounts?: Record<string, NextcloudTalkAccountConfig>;
|
|
83
88
|
/** Optional default account id when multiple accounts are configured. */
|
|
@@ -97,7 +102,7 @@ export type CoreConfig = {
|
|
|
97
102
|
*/
|
|
98
103
|
|
|
99
104
|
/** Actor in the activity (the message sender). */
|
|
100
|
-
|
|
105
|
+
type NextcloudTalkActor = {
|
|
101
106
|
type: "Person";
|
|
102
107
|
/** User ID in Nextcloud. */
|
|
103
108
|
id: string;
|
|
@@ -106,7 +111,7 @@ export type NextcloudTalkActor = {
|
|
|
106
111
|
};
|
|
107
112
|
|
|
108
113
|
/** The message object in the activity. */
|
|
109
|
-
|
|
114
|
+
type NextcloudTalkObject = {
|
|
110
115
|
type: "Note";
|
|
111
116
|
/** Message ID. */
|
|
112
117
|
id: string;
|
|
@@ -119,7 +124,7 @@ export type NextcloudTalkObject = {
|
|
|
119
124
|
};
|
|
120
125
|
|
|
121
126
|
/** Target conversation/room. */
|
|
122
|
-
|
|
127
|
+
type NextcloudTalkTarget = {
|
|
123
128
|
type: "Collection";
|
|
124
129
|
/** Room token. */
|
|
125
130
|
id: string;
|
|
@@ -172,19 +177,17 @@ export type NextcloudTalkWebhookServerOptions = {
|
|
|
172
177
|
path: string;
|
|
173
178
|
secret: string;
|
|
174
179
|
maxBodyBytes?: number;
|
|
180
|
+
authRateLimit?: {
|
|
181
|
+
maxRequests?: number;
|
|
182
|
+
windowMs?: number;
|
|
183
|
+
};
|
|
175
184
|
readBody?: (req: import("node:http").IncomingMessage, maxBodyBytes: number) => Promise<string>;
|
|
176
185
|
isBackendAllowed?: (backend: string) => boolean;
|
|
177
186
|
shouldProcessMessage?: (message: NextcloudTalkInboundMessage) => boolean | Promise<boolean>;
|
|
187
|
+
processMessage?: (
|
|
188
|
+
message: NextcloudTalkInboundMessage,
|
|
189
|
+
) => void | "processed" | "duplicate" | Promise<void | "processed" | "duplicate">;
|
|
178
190
|
onMessage: (message: NextcloudTalkInboundMessage) => void | Promise<void>;
|
|
179
191
|
onError?: (error: Error) => void;
|
|
180
192
|
abortSignal?: AbortSignal;
|
|
181
193
|
};
|
|
182
|
-
|
|
183
|
-
/** Options for sending a message. */
|
|
184
|
-
export type NextcloudTalkSendOptions = {
|
|
185
|
-
baseUrl: string;
|
|
186
|
-
secret: string;
|
|
187
|
-
roomToken: string;
|
|
188
|
-
message: string;
|
|
189
|
-
replyTo?: string;
|
|
190
|
-
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.package-boundary.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "."
|
|
5
|
+
},
|
|
6
|
+
"include": ["./*.ts", "./src/**/*.ts"],
|
|
7
|
+
"exclude": [
|
|
8
|
+
"./**/*.test.ts",
|
|
9
|
+
"./dist/**",
|
|
10
|
+
"./node_modules/**",
|
|
11
|
+
"./src/test-support/**",
|
|
12
|
+
"./src/**/*test-helpers.ts",
|
|
13
|
+
"./src/**/*test-harness.ts",
|
|
14
|
+
"./src/**/*test-support.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
package/src/accounts.test.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { resolveNextcloudTalkAccount } from "./accounts.js";
|
|
6
|
-
import type { CoreConfig } from "./types.js";
|
|
7
|
-
|
|
8
|
-
describe("resolveNextcloudTalkAccount", () => {
|
|
9
|
-
it.runIf(process.platform !== "win32")("rejects symlinked botSecretFile paths", () => {
|
|
10
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-nextcloud-talk-"));
|
|
11
|
-
const secretFile = path.join(dir, "secret.txt");
|
|
12
|
-
const secretLink = path.join(dir, "secret-link.txt");
|
|
13
|
-
fs.writeFileSync(secretFile, "bot-secret\n", "utf8");
|
|
14
|
-
fs.symlinkSync(secretFile, secretLink);
|
|
15
|
-
|
|
16
|
-
const cfg = {
|
|
17
|
-
channels: {
|
|
18
|
-
"nextcloud-talk": {
|
|
19
|
-
baseUrl: "https://cloud.example.com",
|
|
20
|
-
botSecretFile: secretLink,
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
} as CoreConfig;
|
|
24
|
-
|
|
25
|
-
const account = resolveNextcloudTalkAccount({ cfg });
|
|
26
|
-
expect(account.secret).toBe("");
|
|
27
|
-
expect(account.secretSource).toBe("none");
|
|
28
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
29
|
-
});
|
|
30
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { createStartAccountContext } from "../../test-utils/start-account-context.js";
|
|
3
|
-
import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
|
|
4
|
-
|
|
5
|
-
const hoisted = vi.hoisted(() => ({
|
|
6
|
-
monitorNextcloudTalkProvider: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
vi.mock("./monitor.js", async () => {
|
|
10
|
-
const actual = await vi.importActual<typeof import("./monitor.js")>("./monitor.js");
|
|
11
|
-
return {
|
|
12
|
-
...actual,
|
|
13
|
-
monitorNextcloudTalkProvider: hoisted.monitorNextcloudTalkProvider,
|
|
14
|
-
};
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
import { nextcloudTalkPlugin } from "./channel.js";
|
|
18
|
-
|
|
19
|
-
function buildAccount(): ResolvedNextcloudTalkAccount {
|
|
20
|
-
return {
|
|
21
|
-
accountId: "default",
|
|
22
|
-
enabled: true,
|
|
23
|
-
baseUrl: "https://nextcloud.example.com",
|
|
24
|
-
secret: "secret", // pragma: allowlist secret
|
|
25
|
-
secretSource: "config", // pragma: allowlist secret
|
|
26
|
-
config: {
|
|
27
|
-
baseUrl: "https://nextcloud.example.com",
|
|
28
|
-
botSecret: "secret", // pragma: allowlist secret
|
|
29
|
-
webhookPath: "/nextcloud-talk-webhook",
|
|
30
|
-
webhookPort: 8788,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe("nextcloudTalkPlugin gateway.startAccount", () => {
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
vi.clearAllMocks();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("keeps startAccount pending until abort, then stops the monitor", async () => {
|
|
41
|
-
const stop = vi.fn();
|
|
42
|
-
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
|
|
43
|
-
const abort = new AbortController();
|
|
44
|
-
|
|
45
|
-
const task = nextcloudTalkPlugin.gateway!.startAccount!(
|
|
46
|
-
createStartAccountContext({
|
|
47
|
-
account: buildAccount(),
|
|
48
|
-
abortSignal: abort.signal,
|
|
49
|
-
}),
|
|
50
|
-
);
|
|
51
|
-
let settled = false;
|
|
52
|
-
void task.then(() => {
|
|
53
|
-
settled = true;
|
|
54
|
-
});
|
|
55
|
-
await vi.waitFor(() => {
|
|
56
|
-
expect(hoisted.monitorNextcloudTalkProvider).toHaveBeenCalledOnce();
|
|
57
|
-
});
|
|
58
|
-
expect(settled).toBe(false);
|
|
59
|
-
expect(stop).not.toHaveBeenCalled();
|
|
60
|
-
|
|
61
|
-
abort.abort();
|
|
62
|
-
await task;
|
|
63
|
-
|
|
64
|
-
expect(stop).toHaveBeenCalledOnce();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("stops immediately when startAccount receives an already-aborted signal", async () => {
|
|
68
|
-
const stop = vi.fn();
|
|
69
|
-
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
|
|
70
|
-
const abort = new AbortController();
|
|
71
|
-
abort.abort();
|
|
72
|
-
|
|
73
|
-
await nextcloudTalkPlugin.gateway!.startAccount!(
|
|
74
|
-
createStartAccountContext({
|
|
75
|
-
account: buildAccount(),
|
|
76
|
-
abortSignal: abort.signal,
|
|
77
|
-
}),
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
expect(hoisted.monitorNextcloudTalkProvider).toHaveBeenCalledOnce();
|
|
81
|
-
expect(stop).toHaveBeenCalledOnce();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { NextcloudTalkConfigSchema } from "./config-schema.js";
|
|
3
|
-
|
|
4
|
-
describe("NextcloudTalkConfigSchema SecretInput", () => {
|
|
5
|
-
it("accepts SecretRef botSecret and apiPassword at top-level", () => {
|
|
6
|
-
const result = NextcloudTalkConfigSchema.safeParse({
|
|
7
|
-
baseUrl: "https://cloud.example.com",
|
|
8
|
-
botSecret: { source: "env", provider: "default", id: "NEXTCLOUD_TALK_BOT_SECRET" },
|
|
9
|
-
apiUser: "bot",
|
|
10
|
-
apiPassword: { source: "env", provider: "default", id: "NEXTCLOUD_TALK_API_PASSWORD" },
|
|
11
|
-
});
|
|
12
|
-
expect(result.success).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("accepts SecretRef botSecret and apiPassword on account", () => {
|
|
16
|
-
const result = NextcloudTalkConfigSchema.safeParse({
|
|
17
|
-
accounts: {
|
|
18
|
-
main: {
|
|
19
|
-
baseUrl: "https://cloud.example.com",
|
|
20
|
-
botSecret: {
|
|
21
|
-
source: "env",
|
|
22
|
-
provider: "default",
|
|
23
|
-
id: "NEXTCLOUD_TALK_MAIN_BOT_SECRET",
|
|
24
|
-
},
|
|
25
|
-
apiUser: "bot",
|
|
26
|
-
apiPassword: {
|
|
27
|
-
source: "env",
|
|
28
|
-
provider: "default",
|
|
29
|
-
id: "NEXTCLOUD_TALK_MAIN_API_PASSWORD",
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
expect(result.success).toBe(true);
|
|
35
|
-
});
|
|
36
|
-
});
|
package/src/format.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format utilities for Nextcloud Talk messages.
|
|
3
|
-
*
|
|
4
|
-
* Nextcloud Talk supports markdown natively, so most formatting passes through.
|
|
5
|
-
* This module handles any edge cases or transformations needed.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Convert markdown to Nextcloud Talk compatible format.
|
|
10
|
-
* Nextcloud Talk supports standard markdown, so minimal transformation needed.
|
|
11
|
-
*/
|
|
12
|
-
export function markdownToNextcloudTalk(text: string): string {
|
|
13
|
-
return text.trim();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Escape special characters in text to prevent markdown interpretation.
|
|
18
|
-
*/
|
|
19
|
-
export function escapeNextcloudTalkMarkdown(text: string): string {
|
|
20
|
-
return text.replace(/([*_`~[\]()#>+\-=|{}!\\])/g, "\\$1");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Format a mention for a Nextcloud user.
|
|
25
|
-
* Nextcloud Talk uses @user format for mentions.
|
|
26
|
-
*/
|
|
27
|
-
export function formatNextcloudTalkMention(userId: string): string {
|
|
28
|
-
return `@${userId.replace(/^@/, "")}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Format a code block for Nextcloud Talk.
|
|
33
|
-
*/
|
|
34
|
-
export function formatNextcloudTalkCodeBlock(code: string, language?: string): string {
|
|
35
|
-
const lang = language ?? "";
|
|
36
|
-
return `\`\`\`${lang}\n${code}\n\`\`\``;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Format inline code for Nextcloud Talk.
|
|
41
|
-
*/
|
|
42
|
-
export function formatNextcloudTalkInlineCode(code: string): string {
|
|
43
|
-
if (code.includes("`")) {
|
|
44
|
-
return `\`\` ${code} \`\``;
|
|
45
|
-
}
|
|
46
|
-
return `\`${code}\``;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Strip Nextcloud Talk specific formatting from text.
|
|
51
|
-
* Useful for extracting plain text content.
|
|
52
|
-
*/
|
|
53
|
-
export function stripNextcloudTalkFormatting(text: string): string {
|
|
54
|
-
return text
|
|
55
|
-
.replace(/```[\s\S]*?```/g, "")
|
|
56
|
-
.replace(/`[^`]+`/g, "")
|
|
57
|
-
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
58
|
-
.replace(/\*([^*]+)\*/g, "$1")
|
|
59
|
-
.replace(/_([^_]+)_/g, "$1")
|
|
60
|
-
.replace(/~~([^~]+)~~/g, "$1")
|
|
61
|
-
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
62
|
-
.replace(/\s+/g, " ")
|
|
63
|
-
.trim();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Truncate text to a maximum length, preserving word boundaries.
|
|
68
|
-
*/
|
|
69
|
-
export function truncateNextcloudTalkText(text: string, maxLength: number, suffix = "..."): string {
|
|
70
|
-
if (text.length <= maxLength) {
|
|
71
|
-
return text;
|
|
72
|
-
}
|
|
73
|
-
const truncated = text.slice(0, maxLength - suffix.length);
|
|
74
|
-
const lastSpace = truncated.lastIndexOf(" ");
|
|
75
|
-
if (lastSpace > maxLength * 0.7) {
|
|
76
|
-
return truncated.slice(0, lastSpace) + suffix;
|
|
77
|
-
}
|
|
78
|
-
return truncated + suffix;
|
|
79
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { startWebhookServer } from "./monitor.test-harness.js";
|
|
3
|
-
|
|
4
|
-
describe("createNextcloudTalkWebhookServer auth order", () => {
|
|
5
|
-
it("rejects missing signature headers before reading request body", async () => {
|
|
6
|
-
const readBody = vi.fn(async () => {
|
|
7
|
-
throw new Error("should not be called for missing signature headers");
|
|
8
|
-
});
|
|
9
|
-
const harness = await startWebhookServer({
|
|
10
|
-
path: "/nextcloud-auth-order",
|
|
11
|
-
maxBodyBytes: 128,
|
|
12
|
-
readBody,
|
|
13
|
-
onMessage: vi.fn(),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const response = await fetch(harness.webhookUrl, {
|
|
17
|
-
method: "POST",
|
|
18
|
-
headers: {
|
|
19
|
-
"content-type": "application/json",
|
|
20
|
-
},
|
|
21
|
-
body: "{}",
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
expect(response.status).toBe(400);
|
|
25
|
-
expect(await response.json()).toEqual({ error: "Missing signature headers" });
|
|
26
|
-
expect(readBody).not.toHaveBeenCalled();
|
|
27
|
-
});
|
|
28
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { createSignedCreateMessageRequest } from "./monitor.test-fixtures.js";
|
|
3
|
-
import { startWebhookServer } from "./monitor.test-harness.js";
|
|
4
|
-
|
|
5
|
-
describe("createNextcloudTalkWebhookServer backend allowlist", () => {
|
|
6
|
-
it("rejects requests from unexpected backend origins", async () => {
|
|
7
|
-
const onMessage = vi.fn(async () => {});
|
|
8
|
-
const harness = await startWebhookServer({
|
|
9
|
-
path: "/nextcloud-backend-check",
|
|
10
|
-
isBackendAllowed: (backend) => backend === "https://nextcloud.expected",
|
|
11
|
-
onMessage,
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const { body, headers } = createSignedCreateMessageRequest({
|
|
15
|
-
backend: "https://nextcloud.unexpected",
|
|
16
|
-
});
|
|
17
|
-
const response = await fetch(harness.webhookUrl, {
|
|
18
|
-
method: "POST",
|
|
19
|
-
headers,
|
|
20
|
-
body,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
expect(response.status).toBe(401);
|
|
24
|
-
expect(await response.json()).toEqual({ error: "Invalid backend" });
|
|
25
|
-
expect(onMessage).not.toHaveBeenCalled();
|
|
26
|
-
});
|
|
27
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { createMockIncomingRequest } from "../../../test/helpers/mock-incoming-request.js";
|
|
3
|
-
import { readNextcloudTalkWebhookBody } from "./monitor.js";
|
|
4
|
-
|
|
5
|
-
describe("readNextcloudTalkWebhookBody", () => {
|
|
6
|
-
it("reads valid body within max bytes", async () => {
|
|
7
|
-
const req = createMockIncomingRequest(['{"type":"Create"}']);
|
|
8
|
-
const body = await readNextcloudTalkWebhookBody(req, 1024);
|
|
9
|
-
expect(body).toBe('{"type":"Create"}');
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("rejects when payload exceeds max bytes", async () => {
|
|
13
|
-
const req = createMockIncomingRequest(["x".repeat(300)]);
|
|
14
|
-
await expect(readNextcloudTalkWebhookBody(req, 128)).rejects.toThrow("PayloadTooLarge");
|
|
15
|
-
});
|
|
16
|
-
});
|