@bytesbrains/pi-telegram-bridge 1.0.0 → 1.0.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/package.json +1 -1
- package/src/__tests__/helpers.test.ts +783 -0
- package/src/__tests__/index.test.ts +559 -0
- package/src/__tests__/tools.test.ts +251 -0
- package/src/helpers.ts +22 -5
- package/src/index.ts +6 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-telegram-bridge — Tool Schema Tests
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
import { Value } from "typebox/value";
|
|
6
|
+
import {
|
|
7
|
+
listenSchema,
|
|
8
|
+
sendSchema,
|
|
9
|
+
askSchema,
|
|
10
|
+
statusSchema,
|
|
11
|
+
overrideSchema,
|
|
12
|
+
} from "../tools/telegram";
|
|
13
|
+
|
|
14
|
+
// ── listenSchema ──────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
describe("listenSchema", () => {
|
|
17
|
+
it("accepts empty object", () => {
|
|
18
|
+
expect(Value.Check(listenSchema, {})).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("still checks ok with extra properties (by default)", () => {
|
|
22
|
+
// TypeBox Object doesn't strip extra by default unless additionalProperties: false
|
|
23
|
+
expect(Value.Check(listenSchema, { extra: 1 })).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ── sendSchema ────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
describe("sendSchema", () => {
|
|
30
|
+
it("accepts valid message", () => {
|
|
31
|
+
expect(Value.Check(sendSchema, { message: "Hello" })).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("accepts empty message", () => {
|
|
35
|
+
expect(Value.Check(sendSchema, { message: "" })).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("rejects missing message", () => {
|
|
39
|
+
expect(Value.Check(sendSchema, {})).toBe(false);
|
|
40
|
+
expect(Value.Check(sendSchema, { message: 123 })).toBe(false);
|
|
41
|
+
expect(Value.Check(sendSchema, { message: null })).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("accepts long messages", () => {
|
|
45
|
+
expect(Value.Check(sendSchema, { message: "x".repeat(4096) })).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ── askSchema ─────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
describe("askSchema", () => {
|
|
52
|
+
it("accepts minimal valid input (question only)", () => {
|
|
53
|
+
expect(Value.Check(askSchema, { question: "Deploy?" })).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("accepts question with options", () => {
|
|
57
|
+
expect(
|
|
58
|
+
Value.Check(askSchema, {
|
|
59
|
+
question: "Which?",
|
|
60
|
+
options: ["A", "B", "C"],
|
|
61
|
+
}),
|
|
62
|
+
).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("accepts question with timeoutMinutes", () => {
|
|
66
|
+
expect(
|
|
67
|
+
Value.Check(askSchema, {
|
|
68
|
+
question: "Go?",
|
|
69
|
+
timeoutMinutes: 5,
|
|
70
|
+
}),
|
|
71
|
+
).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("accepts all fields", () => {
|
|
75
|
+
expect(
|
|
76
|
+
Value.Check(askSchema, {
|
|
77
|
+
question: "Full test",
|
|
78
|
+
options: ["Yes", "No"],
|
|
79
|
+
timeoutMinutes: 10,
|
|
80
|
+
}),
|
|
81
|
+
).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("rejects missing question", () => {
|
|
85
|
+
expect(Value.Check(askSchema, {})).toBe(false);
|
|
86
|
+
expect(Value.Check(askSchema, { question: 123 })).toBe(false);
|
|
87
|
+
expect(Value.Check(askSchema, { options: ["A"] })).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("accepts empty options array", () => {
|
|
91
|
+
expect(
|
|
92
|
+
Value.Check(askSchema, {
|
|
93
|
+
question: "test",
|
|
94
|
+
options: [],
|
|
95
|
+
}),
|
|
96
|
+
).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("rejects non-array options", () => {
|
|
100
|
+
expect(
|
|
101
|
+
Value.Check(askSchema, {
|
|
102
|
+
question: "test",
|
|
103
|
+
options: "not-array",
|
|
104
|
+
}),
|
|
105
|
+
).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("rejects non-number timeoutMinutes", () => {
|
|
109
|
+
expect(
|
|
110
|
+
Value.Check(askSchema, {
|
|
111
|
+
question: "test",
|
|
112
|
+
timeoutMinutes: "30",
|
|
113
|
+
}),
|
|
114
|
+
).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("accepts timeoutMinutes as 0", () => {
|
|
118
|
+
expect(
|
|
119
|
+
Value.Check(askSchema, {
|
|
120
|
+
question: "test",
|
|
121
|
+
timeoutMinutes: 0,
|
|
122
|
+
}),
|
|
123
|
+
).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ── statusSchema ──────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
describe("statusSchema", () => {
|
|
130
|
+
it("accepts empty object", () => {
|
|
131
|
+
expect(Value.Check(statusSchema, {})).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ── overrideSchema ────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
describe("overrideSchema", () => {
|
|
138
|
+
it("accepts required fields only", () => {
|
|
139
|
+
expect(
|
|
140
|
+
Value.Check(overrideSchema, {
|
|
141
|
+
command: "git push --force",
|
|
142
|
+
reason: "Force push detected",
|
|
143
|
+
}),
|
|
144
|
+
).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("rejects missing command", () => {
|
|
148
|
+
expect(
|
|
149
|
+
Value.Check(overrideSchema, {
|
|
150
|
+
reason: "test",
|
|
151
|
+
}),
|
|
152
|
+
).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("rejects missing reason", () => {
|
|
156
|
+
expect(
|
|
157
|
+
Value.Check(overrideSchema, {
|
|
158
|
+
command: "rm -rf /",
|
|
159
|
+
}),
|
|
160
|
+
).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("accepts all optional fields", () => {
|
|
164
|
+
expect(
|
|
165
|
+
Value.Check(overrideSchema, {
|
|
166
|
+
command: "rm -rf node_modules",
|
|
167
|
+
reason: "Outside project boundary",
|
|
168
|
+
context: "User requested clean reinstall",
|
|
169
|
+
options: ["Yes", "No", "Maybe", "Explain"],
|
|
170
|
+
timeoutMinutes: 15,
|
|
171
|
+
}),
|
|
172
|
+
).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("accepts empty options array", () => {
|
|
176
|
+
expect(
|
|
177
|
+
Value.Check(overrideSchema, {
|
|
178
|
+
command: "test",
|
|
179
|
+
reason: "test",
|
|
180
|
+
options: [],
|
|
181
|
+
}),
|
|
182
|
+
).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("rejects non-string command", () => {
|
|
186
|
+
expect(
|
|
187
|
+
Value.Check(overrideSchema, {
|
|
188
|
+
command: 123,
|
|
189
|
+
reason: "test",
|
|
190
|
+
}),
|
|
191
|
+
).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("rejects non-string reason", () => {
|
|
195
|
+
expect(
|
|
196
|
+
Value.Check(overrideSchema, {
|
|
197
|
+
command: "test",
|
|
198
|
+
reason: null,
|
|
199
|
+
}),
|
|
200
|
+
).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("rejects non-string context", () => {
|
|
204
|
+
expect(
|
|
205
|
+
Value.Check(overrideSchema, {
|
|
206
|
+
command: "test",
|
|
207
|
+
reason: "test",
|
|
208
|
+
context: 123,
|
|
209
|
+
}),
|
|
210
|
+
).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("rejects non-array options", () => {
|
|
214
|
+
expect(
|
|
215
|
+
Value.Check(overrideSchema, {
|
|
216
|
+
command: "test",
|
|
217
|
+
reason: "test",
|
|
218
|
+
options: "yes,no",
|
|
219
|
+
}),
|
|
220
|
+
).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("rejects non-number timeoutMinutes", () => {
|
|
224
|
+
expect(
|
|
225
|
+
Value.Check(overrideSchema, {
|
|
226
|
+
command: "test",
|
|
227
|
+
reason: "test",
|
|
228
|
+
timeoutMinutes: "10",
|
|
229
|
+
}),
|
|
230
|
+
).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("accepts timeoutMinutes as 0", () => {
|
|
234
|
+
expect(
|
|
235
|
+
Value.Check(overrideSchema, {
|
|
236
|
+
command: "test",
|
|
237
|
+
reason: "test",
|
|
238
|
+
timeoutMinutes: 0,
|
|
239
|
+
}),
|
|
240
|
+
).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("accepts long command and reason strings", () => {
|
|
244
|
+
expect(
|
|
245
|
+
Value.Check(overrideSchema, {
|
|
246
|
+
command: "x".repeat(1000),
|
|
247
|
+
reason: "y".repeat(1000),
|
|
248
|
+
}),
|
|
249
|
+
).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
});
|
package/src/helpers.ts
CHANGED
|
@@ -67,7 +67,11 @@ export async function pollReply(
|
|
|
67
67
|
// Drain pending updates to start fresh
|
|
68
68
|
let lastId = 0;
|
|
69
69
|
try {
|
|
70
|
-
const drain = await fetch(`${BASE_URL}${token}/getUpdates
|
|
70
|
+
const drain = await fetch(`${BASE_URL}${token}/getUpdates`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "Content-Type": "application/json" },
|
|
73
|
+
body: JSON.stringify({ timeout: 0 }),
|
|
74
|
+
});
|
|
71
75
|
if (drain.ok) {
|
|
72
76
|
const d = (await drain.json()) as {
|
|
73
77
|
ok: boolean;
|
|
@@ -83,7 +87,12 @@ export async function pollReply(
|
|
|
83
87
|
|
|
84
88
|
while (Date.now() < deadline) {
|
|
85
89
|
const res = await fetch(
|
|
86
|
-
`${BASE_URL}${token}/getUpdates
|
|
90
|
+
`${BASE_URL}${token}/getUpdates`,
|
|
91
|
+
{
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
body: JSON.stringify({ offset: lastId + 1, timeout: 5 }),
|
|
95
|
+
},
|
|
87
96
|
);
|
|
88
97
|
if (!res.ok) {
|
|
89
98
|
await new Promise((r) => setTimeout(r, 3000));
|
|
@@ -154,7 +163,10 @@ export async function seedLastUpdateId(
|
|
|
154
163
|
if (lastUpdateId !== 0) return lastUpdateId;
|
|
155
164
|
const token = getToken();
|
|
156
165
|
try {
|
|
157
|
-
const res = await fetch(`${BASE_URL}${token}/getUpdates
|
|
166
|
+
const res = await fetch(`${BASE_URL}${token}/getUpdates`, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: { "Content-Type": "application/json" },
|
|
169
|
+
body: JSON.stringify({ timeout: 0 }),
|
|
158
170
|
signal,
|
|
159
171
|
});
|
|
160
172
|
if (res.ok) {
|
|
@@ -184,8 +196,13 @@ export async function pollUpdates(
|
|
|
184
196
|
while (!signal.aborted) {
|
|
185
197
|
try {
|
|
186
198
|
const res = await fetch(
|
|
187
|
-
`${BASE_URL}${token}/getUpdates
|
|
188
|
-
{
|
|
199
|
+
`${BASE_URL}${token}/getUpdates`,
|
|
200
|
+
{
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: { "Content-Type": "application/json" },
|
|
203
|
+
body: JSON.stringify({ offset: lastUpdateId + 1, timeout: 10 }),
|
|
204
|
+
signal,
|
|
205
|
+
},
|
|
189
206
|
);
|
|
190
207
|
if (!res.ok) {
|
|
191
208
|
await new Promise((r) => setTimeout(r, 3000));
|
package/src/index.ts
CHANGED
|
@@ -102,7 +102,12 @@ export default function telegramBridge(pi: ExtensionAPI) {
|
|
|
102
102
|
if (!botId) botId = await getBotId();
|
|
103
103
|
|
|
104
104
|
const res = await fetch(
|
|
105
|
-
`https://api.telegram.org/bot${token}/getUpdates
|
|
105
|
+
`https://api.telegram.org/bot${token}/getUpdates`,
|
|
106
|
+
{
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "Content-Type": "application/json" },
|
|
109
|
+
body: JSON.stringify({ offset: lastUpdateId + 1, timeout: 5 }),
|
|
110
|
+
},
|
|
106
111
|
);
|
|
107
112
|
if (!res.ok)
|
|
108
113
|
return {
|