@jira-deploy/core 1.0.5 → 1.0.7
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/constants/users.js +6 -6
- package/package.json +3 -2
- package/scripts/jabber_notify.py +298 -0
- package/tools/jabber.js +29 -4
- package/tools/jabber.test.js +35 -0
- package/tools.test.js +5 -5
package/constants/users.js
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export const USER_MAP = {
|
|
7
7
|
// 格式:'名稱/暱稱(不分大小寫)': 'accountId'
|
|
8
|
-
'Solar Chen': '
|
|
9
|
-
'Rex Li': '
|
|
10
|
-
'James Yu': '
|
|
11
|
-
'Alvin Wang': '
|
|
12
|
-
'Chester Kuo': '
|
|
13
|
-
'Riemann Tseng': '
|
|
8
|
+
'Solar Chen': 'BK00325',
|
|
9
|
+
'Rex Li': 'BK00325',
|
|
10
|
+
'James Yu': 'BK00325',
|
|
11
|
+
'Alvin Wang': 'BK00325',
|
|
12
|
+
'Chester Kuo': 'BK00325',
|
|
13
|
+
'Riemann Tseng': 'BK00325',
|
|
14
14
|
// 'Solar Chen': 'BK00129',
|
|
15
15
|
// 'Rex Li': 'BK00136',
|
|
16
16
|
// 'James Yu': 'BK00178',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jira-deploy/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"repository": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"constants/**/*.js",
|
|
24
|
+
"scripts/**/*.py",
|
|
24
25
|
"tools/**/*.js",
|
|
25
26
|
"*.js"
|
|
26
27
|
],
|
|
@@ -29,6 +30,6 @@
|
|
|
29
30
|
"dotenv": "^16.3.0"
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
32
|
-
"test": "node --test tools.test.js"
|
|
33
|
+
"test": "node --test tools.test.js tools/jabber.test.js"
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
jabber_notify.py — Send a Jabber message to a MUC room or a specific user.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python3 src/scripts/jabber_notify.py "<message>"
|
|
7
|
+
|
|
8
|
+
Required environment variables:
|
|
9
|
+
JABBER_SERVER
|
|
10
|
+
JABBER_USER
|
|
11
|
+
JABBER_DOMAIN
|
|
12
|
+
JABBER_KEYCHAIN_SERVICE
|
|
13
|
+
JABBER_KEYCHAIN_ACCOUNT
|
|
14
|
+
|
|
15
|
+
One of the following must be set:
|
|
16
|
+
JABBER_ROOM — MUC room JID (e.g. webqa@conference.linebank.com.tw)
|
|
17
|
+
JABBER_TO — Direct message recipient JID (e.g. BK00178@linebank.com.tw)
|
|
18
|
+
|
|
19
|
+
Optional environment variables:
|
|
20
|
+
JABBER_PORT (defaults to 5222)
|
|
21
|
+
JABBER_RESOURCE (defaults to copilot)
|
|
22
|
+
JABBER_NICK (defaults to Copilot) — only used for MUC
|
|
23
|
+
|
|
24
|
+
To add or update Keychain entry:
|
|
25
|
+
security add-generic-password -a "$JABBER_KEYCHAIN_ACCOUNT" -s "$JABBER_KEYCHAIN_SERVICE" -w
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import base64
|
|
29
|
+
import os
|
|
30
|
+
import socket
|
|
31
|
+
import ssl
|
|
32
|
+
import subprocess
|
|
33
|
+
import sys
|
|
34
|
+
import time
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def required_env(key: str) -> str:
|
|
38
|
+
value = os.getenv(key, "").strip()
|
|
39
|
+
if not value:
|
|
40
|
+
raise RuntimeError(f"Missing required environment variable: {key}")
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def load_config() -> dict:
|
|
45
|
+
room = os.getenv("JABBER_ROOM", "").strip()
|
|
46
|
+
to = os.getenv("JABBER_TO", "").strip()
|
|
47
|
+
if not room and not to:
|
|
48
|
+
raise RuntimeError("Either JABBER_ROOM or JABBER_TO must be set")
|
|
49
|
+
return {
|
|
50
|
+
"server": required_env("JABBER_SERVER"),
|
|
51
|
+
"port": int(os.getenv("JABBER_PORT", "5222")),
|
|
52
|
+
"user": required_env("JABBER_USER"),
|
|
53
|
+
"domain": required_env("JABBER_DOMAIN"),
|
|
54
|
+
"resource": os.getenv("JABBER_RESOURCE", "copilot").strip() or "copilot",
|
|
55
|
+
"room": room,
|
|
56
|
+
"to": to,
|
|
57
|
+
"nick": os.getenv("JABBER_NICK", "Copilot").strip() or "Copilot",
|
|
58
|
+
"keychain_service": required_env("JABBER_KEYCHAIN_SERVICE"),
|
|
59
|
+
"keychain_account": required_env("JABBER_KEYCHAIN_ACCOUNT"),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def recv_all(sock, timeout=2):
|
|
64
|
+
sock.settimeout(timeout)
|
|
65
|
+
data = b""
|
|
66
|
+
try:
|
|
67
|
+
while True:
|
|
68
|
+
chunk = sock.recv(8192)
|
|
69
|
+
if not chunk:
|
|
70
|
+
break
|
|
71
|
+
data += chunk
|
|
72
|
+
except socket.timeout:
|
|
73
|
+
pass
|
|
74
|
+
return data.decode(errors="replace")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_jabber_password(keychain_service: str, keychain_account: str) -> str:
|
|
78
|
+
# Keychain credential must be created before using this script.
|
|
79
|
+
# Example setup:
|
|
80
|
+
# security add-generic-password -a "$JABBER_KEYCHAIN_ACCOUNT" -s "$JABBER_KEYCHAIN_SERVICE" -w
|
|
81
|
+
result = subprocess.run(
|
|
82
|
+
[
|
|
83
|
+
"security",
|
|
84
|
+
"find-generic-password",
|
|
85
|
+
"-s",
|
|
86
|
+
keychain_service,
|
|
87
|
+
"-a",
|
|
88
|
+
keychain_account,
|
|
89
|
+
"-w",
|
|
90
|
+
],
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
)
|
|
94
|
+
if result.returncode != 0:
|
|
95
|
+
raise RuntimeError(
|
|
96
|
+
"Cannot read Jabber password from Keychain. "
|
|
97
|
+
"Check JABBER_KEYCHAIN_SERVICE and JABBER_KEYCHAIN_ACCOUNT."
|
|
98
|
+
)
|
|
99
|
+
return result.stdout.strip()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _extract_error_reason(xml_text: str) -> str:
|
|
103
|
+
reasons = [
|
|
104
|
+
"not-authorized",
|
|
105
|
+
"forbidden",
|
|
106
|
+
"conflict",
|
|
107
|
+
"service-unavailable",
|
|
108
|
+
"item-not-found",
|
|
109
|
+
"not-allowed",
|
|
110
|
+
"registration-required",
|
|
111
|
+
"remote-server-not-found",
|
|
112
|
+
"internal-server-error",
|
|
113
|
+
]
|
|
114
|
+
for reason in reasons:
|
|
115
|
+
if f"<{reason}" in xml_text:
|
|
116
|
+
return reason
|
|
117
|
+
if "<error" in xml_text:
|
|
118
|
+
return "unknown"
|
|
119
|
+
return ""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _wait_for_join_confirmation(tls, room: str, nick: str, timeout_seconds: int = 10) -> None:
|
|
123
|
+
deadline = time.time() + timeout_seconds
|
|
124
|
+
self_presence_markers = [
|
|
125
|
+
'<status code="110"',
|
|
126
|
+
"<status code='110'",
|
|
127
|
+
]
|
|
128
|
+
explicit_nick_markers = [
|
|
129
|
+
f'from="{room}/{nick}"',
|
|
130
|
+
f"from='{room}/{nick}'",
|
|
131
|
+
]
|
|
132
|
+
room_presence_markers = [
|
|
133
|
+
f'from="{room}/',
|
|
134
|
+
f"from='{room}/",
|
|
135
|
+
]
|
|
136
|
+
saw_room_presence = False
|
|
137
|
+
|
|
138
|
+
while time.time() < deadline:
|
|
139
|
+
chunk = recv_all(tls, 1)
|
|
140
|
+
if not chunk:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
error_reason = _extract_error_reason(chunk)
|
|
144
|
+
if error_reason:
|
|
145
|
+
raise RuntimeError(f"Jabber room join failed: {error_reason}")
|
|
146
|
+
|
|
147
|
+
if "<presence" in chunk and any(marker in chunk for marker in room_presence_markers):
|
|
148
|
+
saw_room_presence = True
|
|
149
|
+
if "type=\"error\"" in chunk or "type='error'" in chunk:
|
|
150
|
+
raise RuntimeError("Jabber room join failed: error")
|
|
151
|
+
if "type=\"unavailable\"" in chunk or "type='unavailable'" in chunk:
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
if any(marker in chunk for marker in self_presence_markers):
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
if any(marker in chunk for marker in explicit_nick_markers):
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
if saw_room_presence:
|
|
161
|
+
return
|
|
162
|
+
raise RuntimeError("Jabber room join not confirmed")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _wait_for_message_confirmation(
|
|
166
|
+
tls,
|
|
167
|
+
room: str,
|
|
168
|
+
msg_id: str,
|
|
169
|
+
timeout_seconds: int = 5,
|
|
170
|
+
) -> None:
|
|
171
|
+
deadline = time.time() + timeout_seconds
|
|
172
|
+
id_markers = [f'id="{msg_id}"', f"id='{msg_id}'"]
|
|
173
|
+
|
|
174
|
+
while time.time() < deadline:
|
|
175
|
+
chunk = recv_all(tls, 1)
|
|
176
|
+
if not chunk:
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
if "<message" in chunk and any(marker in chunk for marker in id_markers):
|
|
180
|
+
if "type=\"error\"" in chunk or "type='error'" in chunk:
|
|
181
|
+
error_reason = _extract_error_reason(chunk) or "error"
|
|
182
|
+
raise RuntimeError(f"Jabber message rejected: {error_reason}")
|
|
183
|
+
|
|
184
|
+
if "<message" in chunk and (
|
|
185
|
+
"type=\"error\"" in chunk or "type='error'" in chunk
|
|
186
|
+
):
|
|
187
|
+
error_reason = _extract_error_reason(chunk) or "error"
|
|
188
|
+
raise RuntimeError(f"Jabber message rejected: {error_reason}")
|
|
189
|
+
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def send_to_room(message: str) -> None:
|
|
194
|
+
config = load_config()
|
|
195
|
+
password = get_jabber_password(
|
|
196
|
+
config["keychain_service"],
|
|
197
|
+
config["keychain_account"],
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
sasl_plain = base64.b64encode(f"\x00{config['user']}\x00{password}".encode()).decode()
|
|
201
|
+
|
|
202
|
+
ctx = ssl.create_default_context()
|
|
203
|
+
ctx.check_hostname = False
|
|
204
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
205
|
+
|
|
206
|
+
raw = None
|
|
207
|
+
tls = None
|
|
208
|
+
try:
|
|
209
|
+
# ── TCP connect ──────────────────────────────────────────────────────
|
|
210
|
+
raw = socket.create_connection((config["server"], config["port"]), timeout=10)
|
|
211
|
+
raw.sendall(
|
|
212
|
+
f"<?xml version='1.0'?><stream:stream xmlns='jabber:client' version='1.0' "
|
|
213
|
+
f"xmlns:stream='http://etherx.jabber.org/streams' to='{config['domain']}'>".encode()
|
|
214
|
+
)
|
|
215
|
+
time.sleep(0.3)
|
|
216
|
+
recv_all(raw, 1)
|
|
217
|
+
|
|
218
|
+
# ── STARTTLS ─────────────────────────────────────────────────────────
|
|
219
|
+
raw.sendall(b"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
|
|
220
|
+
time.sleep(0.3)
|
|
221
|
+
recv_all(raw, 1)
|
|
222
|
+
tls = ctx.wrap_socket(raw, server_hostname=config["server"])
|
|
223
|
+
|
|
224
|
+
# ── Re-open stream over TLS ──────────────────────────────────────────
|
|
225
|
+
tls.sendall(
|
|
226
|
+
f"<?xml version='1.0'?><stream:stream xmlns='jabber:client' version='1.0' "
|
|
227
|
+
f"xmlns:stream='http://etherx.jabber.org/streams' to='{config['domain']}'>".encode()
|
|
228
|
+
)
|
|
229
|
+
time.sleep(0.3)
|
|
230
|
+
recv_all(tls, 1)
|
|
231
|
+
|
|
232
|
+
# ── SASL PLAIN auth ──────────────────────────────────────────────────
|
|
233
|
+
tls.sendall(
|
|
234
|
+
f"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>{sasl_plain}</auth>".encode()
|
|
235
|
+
)
|
|
236
|
+
time.sleep(1)
|
|
237
|
+
auth_resp = recv_all(tls, 2)
|
|
238
|
+
if "success" not in auth_resp:
|
|
239
|
+
raise RuntimeError("Jabber authentication failed")
|
|
240
|
+
|
|
241
|
+
# ── Re-open stream after auth ────────────────────────────────────────
|
|
242
|
+
tls.sendall(
|
|
243
|
+
f"<?xml version='1.0'?><stream:stream xmlns='jabber:client' version='1.0' "
|
|
244
|
+
f"xmlns:stream='http://etherx.jabber.org/streams' to='{config['domain']}'>".encode()
|
|
245
|
+
)
|
|
246
|
+
time.sleep(0.5)
|
|
247
|
+
recv_all(tls, 1)
|
|
248
|
+
|
|
249
|
+
# ── Resource bind ────────────────────────────────────────────────────
|
|
250
|
+
tls.sendall(
|
|
251
|
+
f'<iq type="set" id="b1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">'
|
|
252
|
+
f'<resource>{config["resource"]}</resource></bind></iq>'.encode()
|
|
253
|
+
)
|
|
254
|
+
time.sleep(0.5)
|
|
255
|
+
bind_resp = recv_all(tls, 2)
|
|
256
|
+
bind_error = _extract_error_reason(bind_resp)
|
|
257
|
+
if bind_error:
|
|
258
|
+
raise RuntimeError(f"Jabber resource bind failed: {bind_error}")
|
|
259
|
+
|
|
260
|
+
safe_msg = message.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
261
|
+
msg_id = f"notify-{int(time.time() * 1000)}"
|
|
262
|
+
|
|
263
|
+
if config["to"]:
|
|
264
|
+
# ── Direct message ───────────────────────────────────────────────
|
|
265
|
+
tls.sendall(
|
|
266
|
+
f'<message id="{msg_id}" to="{config["to"]}" type="chat"><body>{safe_msg}</body></message>'.encode()
|
|
267
|
+
)
|
|
268
|
+
time.sleep(0.5)
|
|
269
|
+
else:
|
|
270
|
+
# ── Join MUC room ────────────────────────────────────────────────
|
|
271
|
+
tls.sendall(
|
|
272
|
+
f'<presence to="{config["room"]}/{config["nick"]}"><x xmlns="http://jabber.org/protocol/muc"/></presence>'.encode()
|
|
273
|
+
)
|
|
274
|
+
_wait_for_join_confirmation(tls, config["room"], config["nick"], timeout_seconds=20)
|
|
275
|
+
|
|
276
|
+
# ── Send MUC message ─────────────────────────────────────────────
|
|
277
|
+
tls.sendall(
|
|
278
|
+
f'<message id="{msg_id}" to="{config["room"]}" type="groupchat"><body>{safe_msg}</body></message>'.encode()
|
|
279
|
+
)
|
|
280
|
+
_wait_for_message_confirmation(tls, config["room"], msg_id, timeout_seconds=5)
|
|
281
|
+
|
|
282
|
+
# ── Leave room ───────────────────────────────────────────────────
|
|
283
|
+
tls.sendall(f'<presence to="{config["room"]}/{config["nick"]}" type="unavailable"/>'.encode())
|
|
284
|
+
time.sleep(0.3)
|
|
285
|
+
finally:
|
|
286
|
+
if tls is not None:
|
|
287
|
+
tls.close()
|
|
288
|
+
elif raw is not None:
|
|
289
|
+
raw.close()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
if len(sys.argv) < 2:
|
|
294
|
+
print("Usage: python3 src/scripts/jabber_notify.py '<message>'")
|
|
295
|
+
sys.exit(1)
|
|
296
|
+
msg = sys.argv[1]
|
|
297
|
+
send_to_room(msg)
|
|
298
|
+
print("Jabber message sent")
|
package/tools/jabber.js
CHANGED
|
@@ -7,11 +7,36 @@ import fs from 'fs';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import {error, ok} from './helpers.js';
|
|
9
9
|
|
|
10
|
-
function
|
|
10
|
+
function realpathOrNull(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
return fs.realpathSync(filePath);
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function unique(values) {
|
|
19
|
+
return [...new Set(values.filter(Boolean))];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveJabberNotifyScriptPath({
|
|
23
|
+
env = process.env,
|
|
24
|
+
execPath = process.execPath,
|
|
25
|
+
cwd = process.cwd(),
|
|
26
|
+
} = {}) {
|
|
27
|
+
if (env.JABBER_NOTIFY_SCRIPT) {
|
|
28
|
+
return env.JABBER_NOTIFY_SCRIPT;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const resolvedExecPath = realpathOrNull(execPath);
|
|
11
32
|
const candidates = [
|
|
12
|
-
|
|
13
|
-
|
|
33
|
+
...unique([resolvedExecPath, execPath]).map((candidate) =>
|
|
34
|
+
path.resolve(path.dirname(candidate), 'scripts/jabber_notify.py')
|
|
35
|
+
),
|
|
36
|
+
path.resolve(cwd, 'packages/jira-core/scripts/jabber_notify.py'),
|
|
37
|
+
path.resolve(cwd, 'scripts/jabber_notify.py'),
|
|
14
38
|
];
|
|
39
|
+
|
|
15
40
|
return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
|
|
16
41
|
}
|
|
17
42
|
|
|
@@ -49,7 +74,7 @@ export function getJabberToolDefinitions() {
|
|
|
49
74
|
// ── Handler ───────────────────────────────────────────────────────
|
|
50
75
|
|
|
51
76
|
export async function handleSendJabberMessage(args, _ctx) {
|
|
52
|
-
const scriptPath =
|
|
77
|
+
const scriptPath = resolveJabberNotifyScriptPath();
|
|
53
78
|
|
|
54
79
|
// dryRun 模式:不實際發送,直接回傳預覽
|
|
55
80
|
if (args.dryRun) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtempSync, mkdirSync, writeFileSync} from 'node:fs';
|
|
4
|
+
import {tmpdir} from 'node:os';
|
|
5
|
+
import {join} from 'node:path';
|
|
6
|
+
|
|
7
|
+
import {resolveJabberNotifyScriptPath} from './jabber.js';
|
|
8
|
+
|
|
9
|
+
test('resolveJabberNotifyScriptPath ignores empty env and prefers packaged script beside binary', () => {
|
|
10
|
+
const root = mkdtempSync(join(tmpdir(), 'ares-jabber-'));
|
|
11
|
+
const binDir = join(root, 'app');
|
|
12
|
+
const scriptPath = join(binDir, 'scripts', 'jabber_notify.py');
|
|
13
|
+
mkdirSync(join(binDir, 'scripts'), {recursive: true});
|
|
14
|
+
writeFileSync(scriptPath, '#!/usr/bin/env python3\n');
|
|
15
|
+
|
|
16
|
+
assert.equal(
|
|
17
|
+
resolveJabberNotifyScriptPath({
|
|
18
|
+
env: {JABBER_NOTIFY_SCRIPT: ''},
|
|
19
|
+
execPath: join(binDir, 'ares'),
|
|
20
|
+
cwd: join(root, 'work'),
|
|
21
|
+
}),
|
|
22
|
+
scriptPath,
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('resolveJabberNotifyScriptPath keeps explicit env override', () => {
|
|
27
|
+
assert.equal(
|
|
28
|
+
resolveJabberNotifyScriptPath({
|
|
29
|
+
env: {JABBER_NOTIFY_SCRIPT: '/custom/jabber_notify.py'},
|
|
30
|
+
execPath: '/opt/ares/ares',
|
|
31
|
+
cwd: '/tmp',
|
|
32
|
+
}),
|
|
33
|
+
'/custom/jabber_notify.py',
|
|
34
|
+
);
|
|
35
|
+
});
|
package/tools.test.js
CHANGED
|
@@ -1666,7 +1666,7 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1666
1666
|
assert.ok(!result.content[0].text.startsWith('❌'), 'STG approval should continue');
|
|
1667
1667
|
const data = JSON.parse(result.content[0].text);
|
|
1668
1668
|
assert.equal(data.finalStatus, 'VERIFY');
|
|
1669
|
-
assert.deepEqual(jira.calls.updateAssignee, [{ issueKey: 'CID-100', accountId: '
|
|
1669
|
+
assert.deepEqual(jira.calls.updateAssignee, [{ issueKey: 'CID-100', accountId: 'BK00325' }]);
|
|
1670
1670
|
} finally {
|
|
1671
1671
|
await new Promise((resolve, reject) => {
|
|
1672
1672
|
server.close((err) => (err ? reject(err) : resolve()));
|
|
@@ -1683,7 +1683,7 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1683
1683
|
process.env.POLL_TIMEOUT_MS = '10';
|
|
1684
1684
|
|
|
1685
1685
|
const jira = makeApprovalJira('uat', {
|
|
1686
|
-
comments: [{ body: 'Approved, please proceed', author: { name: '
|
|
1686
|
+
comments: [{ body: 'Approved, please proceed', author: { name: 'BK00325', displayName: 'James Yu' } }],
|
|
1687
1687
|
});
|
|
1688
1688
|
const result = await executeTool(
|
|
1689
1689
|
'auto_grayrelease',
|
|
@@ -1695,8 +1695,8 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1695
1695
|
const data = JSON.parse(result.content[0].text);
|
|
1696
1696
|
assert.equal(data.finalStatus, 'VERIFY');
|
|
1697
1697
|
assert.deepEqual(jira.calls.updateAssignee, [
|
|
1698
|
-
{ issueKey: 'CID-101', accountId: '
|
|
1699
|
-
{ issueKey: 'CID-101', accountId: '
|
|
1698
|
+
{ issueKey: 'CID-101', accountId: 'BK00325' },
|
|
1699
|
+
{ issueKey: 'CID-101', accountId: 'BK00325' },
|
|
1700
1700
|
]);
|
|
1701
1701
|
} finally {
|
|
1702
1702
|
restoreEnv();
|
|
@@ -1711,7 +1711,7 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1711
1711
|
process.env.POLL_TIMEOUT_MS = '10';
|
|
1712
1712
|
|
|
1713
1713
|
const jira = makeApprovalJira('uat', {
|
|
1714
|
-
comments: [{ body: 'Approved, please proceed', author: { name: '
|
|
1714
|
+
comments: [{ body: 'Approved, please proceed', author: { name: 'BK00325', displayName: 'James Yu' } }],
|
|
1715
1715
|
});
|
|
1716
1716
|
const result = await executeTool(
|
|
1717
1717
|
'auto_grayrelease',
|