bingocode 1.0.40 → 1.1.42
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/bin/bingo-win.cjs +2 -1
- package/bin/bingocode-win.cjs +2 -1
- package/bin/claude-win.cjs +2 -1
- package/bun.lock +1716 -0
- package/package.json +14 -2
- package/src/server/config/providers.yaml +1 -1
- package/src/server/proxy/transform/anthropicToOpenaiChat.ts +23 -9
- package/adapters/README.md +0 -87
- package/adapters/common/__tests__/chat-queue.test.ts +0 -61
- package/adapters/common/__tests__/format.test.ts +0 -148
- package/adapters/common/__tests__/http-client.test.ts +0 -105
- package/adapters/common/__tests__/message-buffer.test.ts +0 -84
- package/adapters/common/__tests__/message-dedup.test.ts +0 -57
- package/adapters/common/__tests__/session-store.test.ts +0 -62
- package/adapters/common/__tests__/ws-bridge.test.ts +0 -177
- package/adapters/common/attachment/__tests__/attachment-limits.test.ts +0 -52
- package/adapters/common/attachment/__tests__/attachment-store.test.ts +0 -108
- package/adapters/common/attachment/__tests__/image-block-watcher.test.ts +0 -115
- package/adapters/common/attachment/attachment-limits.ts +0 -58
- package/adapters/common/attachment/attachment-store.ts +0 -121
- package/adapters/common/attachment/attachment-types.ts +0 -29
- package/adapters/common/attachment/image-block-watcher.ts +0 -94
- package/adapters/common/chat-queue.ts +0 -24
- package/adapters/common/config.ts +0 -96
- package/adapters/common/format.ts +0 -229
- package/adapters/common/http-client.ts +0 -107
- package/adapters/common/message-buffer.ts +0 -91
- package/adapters/common/message-dedup.ts +0 -57
- package/adapters/common/pairing.ts +0 -149
- package/adapters/common/session-store.ts +0 -60
- package/adapters/common/ws-bridge.ts +0 -282
- package/adapters/feishu/__tests__/card-errors.test.ts +0 -194
- package/adapters/feishu/__tests__/cardkit.test.ts +0 -295
- package/adapters/feishu/__tests__/extract-payload.test.ts +0 -77
- package/adapters/feishu/__tests__/feishu.test.ts +0 -907
- package/adapters/feishu/__tests__/flush-controller.test.ts +0 -290
- package/adapters/feishu/__tests__/markdown-style.test.ts +0 -353
- package/adapters/feishu/__tests__/media.test.ts +0 -120
- package/adapters/feishu/__tests__/streaming-card.test.ts +0 -914
- package/adapters/feishu/card-errors.ts +0 -151
- package/adapters/feishu/cardkit.ts +0 -294
- package/adapters/feishu/extract-payload.ts +0 -95
- package/adapters/feishu/flush-controller.ts +0 -149
- package/adapters/feishu/index.ts +0 -1275
- package/adapters/feishu/markdown-style.ts +0 -212
- package/adapters/feishu/media.ts +0 -176
- package/adapters/feishu/streaming-card.ts +0 -612
- package/adapters/package.json +0 -23
- package/adapters/telegram/__tests__/media.test.ts +0 -86
- package/adapters/telegram/__tests__/telegram.test.ts +0 -115
- package/adapters/telegram/index.ts +0 -754
- package/adapters/telegram/media.ts +0 -89
- package/adapters/tsconfig.json +0 -18
- package/runtime/mac_helper.py +0 -775
- package/runtime/requirements-win.txt +0 -7
- package/runtime/requirements.txt +0 -6
- package/runtime/test_helpers.py +0 -322
- package/runtime/win_helper.py +0 -723
- package/scripts/count-app-loc.ts +0 -256
- package/scripts/release.ts +0 -130
- package/start-cli.bat +0 -7
- package/stubs/ant-claude-for-chrome-mcp.ts +0 -24
- package/stubs/color-diff-napi.ts +0 -45
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* card-errors 单元测试
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'bun:test'
|
|
6
|
-
import {
|
|
7
|
-
CARD_ERROR,
|
|
8
|
-
CARD_CONTENT_SUB_ERROR,
|
|
9
|
-
extractLarkApiCode,
|
|
10
|
-
extractSubCode,
|
|
11
|
-
parseCardApiError,
|
|
12
|
-
isCardRateLimitError,
|
|
13
|
-
isCardTableLimitError,
|
|
14
|
-
} from '../card-errors.js'
|
|
15
|
-
|
|
16
|
-
describe('extractLarkApiCode', () => {
|
|
17
|
-
it('从 err.code 直接提取', () => {
|
|
18
|
-
expect(extractLarkApiCode({ code: 230020 })).toBe(230020)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('从 err.data.code 提取', () => {
|
|
22
|
-
expect(extractLarkApiCode({ data: { code: 230099 } })).toBe(230099)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('从 err.response.data.code 提取(Axios 风格)', () => {
|
|
26
|
-
expect(extractLarkApiCode({ response: { data: { code: 99991672 } } })).toBe(99991672)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('数字字符串被强制转成 number', () => {
|
|
30
|
-
expect(extractLarkApiCode({ code: '230020' })).toBe(230020)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('三层结构优先级: err.code > err.data.code > err.response.data.code', () => {
|
|
34
|
-
const err = {
|
|
35
|
-
code: 1,
|
|
36
|
-
data: { code: 2 },
|
|
37
|
-
response: { data: { code: 3 } },
|
|
38
|
-
}
|
|
39
|
-
expect(extractLarkApiCode(err)).toBe(1)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('none → undefined', () => {
|
|
43
|
-
expect(extractLarkApiCode({})).toBeUndefined()
|
|
44
|
-
expect(extractLarkApiCode(null)).toBeUndefined()
|
|
45
|
-
expect(extractLarkApiCode(undefined)).toBeUndefined()
|
|
46
|
-
expect(extractLarkApiCode('just a string')).toBeUndefined()
|
|
47
|
-
expect(extractLarkApiCode(new Error('plain'))).toBeUndefined()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('非有限数字被忽略', () => {
|
|
51
|
-
expect(extractLarkApiCode({ code: NaN })).toBeUndefined()
|
|
52
|
-
expect(extractLarkApiCode({ code: 'not-a-number' })).toBeUndefined()
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
describe('extractSubCode', () => {
|
|
57
|
-
it('识别标准的 ErrCode: 11310', () => {
|
|
58
|
-
const msg = 'Failed to create card content, ext=ErrCode: 11310; ErrMsg: card table number over limit'
|
|
59
|
-
expect(extractSubCode(msg)).toBe(11310)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('无 ErrCode 时返回 null', () => {
|
|
63
|
-
expect(extractSubCode('random error message')).toBeNull()
|
|
64
|
-
expect(extractSubCode('')).toBeNull()
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('ErrCode 大小写容错(冒号后多空格)', () => {
|
|
68
|
-
expect(extractSubCode('ErrCode: 42')).toBe(42)
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
describe('parseCardApiError', () => {
|
|
73
|
-
it('从 SDK 风格错误提取完整结构', () => {
|
|
74
|
-
const err = { code: 230099, msg: 'ErrCode: 11310; ErrMsg: card table number over limit' }
|
|
75
|
-
const parsed = parseCardApiError(err)
|
|
76
|
-
expect(parsed).toEqual({
|
|
77
|
-
code: 230099,
|
|
78
|
-
subCode: 11310,
|
|
79
|
-
errMsg: 'ErrCode: 11310; ErrMsg: card table number over limit',
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('从 Axios 风格错误(response.data.msg)提取', () => {
|
|
84
|
-
const err = {
|
|
85
|
-
response: {
|
|
86
|
-
data: {
|
|
87
|
-
code: 230020,
|
|
88
|
-
msg: 'rate limited',
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
}
|
|
92
|
-
const parsed = parseCardApiError(err)
|
|
93
|
-
expect(parsed?.code).toBe(230020)
|
|
94
|
-
expect(parsed?.errMsg).toBe('rate limited')
|
|
95
|
-
expect(parsed?.subCode).toBeNull()
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('无 code 时返回 null', () => {
|
|
99
|
-
expect(parseCardApiError({})).toBeNull()
|
|
100
|
-
expect(parseCardApiError(null)).toBeNull()
|
|
101
|
-
expect(parseCardApiError('string')).toBeNull()
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('有 code 无 msg 时 errMsg 为空字符串', () => {
|
|
105
|
-
const parsed = parseCardApiError({ code: 230020 })
|
|
106
|
-
expect(parsed).toEqual({ code: 230020, subCode: null, errMsg: '' })
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('fallback 到 err.message', () => {
|
|
110
|
-
const err = Object.assign(new Error('fallback text'), { code: 230099 })
|
|
111
|
-
const parsed = parseCardApiError(err)
|
|
112
|
-
expect(parsed?.errMsg).toBe('fallback text')
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
describe('isCardRateLimitError', () => {
|
|
117
|
-
it('识别 230020', () => {
|
|
118
|
-
expect(isCardRateLimitError({ code: 230020 })).toBe(true)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('识别 Axios 风格 230020', () => {
|
|
122
|
-
expect(isCardRateLimitError({ response: { data: { code: 230020 } } })).toBe(true)
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('不匹配其他 code', () => {
|
|
126
|
-
expect(isCardRateLimitError({ code: 230099 })).toBe(false)
|
|
127
|
-
expect(isCardRateLimitError({ code: 99991672 })).toBe(false)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('非错误对象返回 false', () => {
|
|
131
|
-
expect(isCardRateLimitError(null)).toBe(false)
|
|
132
|
-
expect(isCardRateLimitError({})).toBe(false)
|
|
133
|
-
expect(isCardRateLimitError(new Error('random'))).toBe(false)
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
describe('isCardTableLimitError', () => {
|
|
138
|
-
const validMsg = 'Failed to create card content, ext=ErrCode: 11310; ErrMsg: card table number over limit; ErrorValue: table; '
|
|
139
|
-
|
|
140
|
-
it('严格三条件匹配: code=230099 + subCode=11310 + msg 含 table number over limit', () => {
|
|
141
|
-
const err = { code: CARD_ERROR.CARD_CONTENT_FAILED, msg: validMsg }
|
|
142
|
-
expect(isCardTableLimitError(err)).toBe(true)
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it('从 Axios 风格的 response.data 匹配', () => {
|
|
146
|
-
const err = {
|
|
147
|
-
response: {
|
|
148
|
-
data: {
|
|
149
|
-
code: 230099,
|
|
150
|
-
msg: validMsg,
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
}
|
|
154
|
-
expect(isCardTableLimitError(err)).toBe(true)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('230099 + 11310 但没有 "table number over limit" 字样 → false(其它元素超限)', () => {
|
|
158
|
-
const err = {
|
|
159
|
-
code: CARD_ERROR.CARD_CONTENT_FAILED,
|
|
160
|
-
msg: 'ErrCode: 11310; ErrMsg: some other element limit; ',
|
|
161
|
-
}
|
|
162
|
-
expect(isCardTableLimitError(err)).toBe(false)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('code 不是 230099 → false', () => {
|
|
166
|
-
const err = { code: 230020, msg: validMsg }
|
|
167
|
-
expect(isCardTableLimitError(err)).toBe(false)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('没有 subCode → false', () => {
|
|
171
|
-
const err = { code: 230099, msg: 'card table number over limit (no ErrCode)' }
|
|
172
|
-
expect(isCardTableLimitError(err)).toBe(false)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('不区分 "table number" 的大小写', () => {
|
|
176
|
-
const err = {
|
|
177
|
-
code: 230099,
|
|
178
|
-
msg: 'ErrCode: 11310; ErrMsg: CARD TABLE NUMBER OVER LIMIT; ',
|
|
179
|
-
}
|
|
180
|
-
expect(isCardTableLimitError(err)).toBe(true)
|
|
181
|
-
})
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
describe('常量值', () => {
|
|
185
|
-
it('CARD_ERROR.RATE_LIMITED === 230020', () => {
|
|
186
|
-
expect(CARD_ERROR.RATE_LIMITED).toBe(230020)
|
|
187
|
-
})
|
|
188
|
-
it('CARD_ERROR.CARD_CONTENT_FAILED === 230099', () => {
|
|
189
|
-
expect(CARD_ERROR.CARD_CONTENT_FAILED).toBe(230099)
|
|
190
|
-
})
|
|
191
|
-
it('CARD_CONTENT_SUB_ERROR.ELEMENT_LIMIT === 11310', () => {
|
|
192
|
-
expect(CARD_CONTENT_SUB_ERROR.ELEMENT_LIMIT).toBe(11310)
|
|
193
|
-
})
|
|
194
|
-
})
|
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cardkit.ts 单元测试
|
|
3
|
-
*
|
|
4
|
-
* 不调用真实的 Lark API —— 用 mock client 捕获调用参数,验证:
|
|
5
|
-
* - 每个函数构造的 payload 结构
|
|
6
|
-
* - 非零 code 响应抛出 CardKitApiError(可被 card-errors 识别)
|
|
7
|
-
* - 缺失关键字段时抛错
|
|
8
|
-
* - sequence 正确传递
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { describe, it, expect } from 'bun:test'
|
|
12
|
-
import {
|
|
13
|
-
createCardEntity,
|
|
14
|
-
sendCardAsMessage,
|
|
15
|
-
streamCardContent,
|
|
16
|
-
setCardStreamingMode,
|
|
17
|
-
updateCardKitCard,
|
|
18
|
-
CardKitApiError,
|
|
19
|
-
STREAMING_ELEMENT_ID,
|
|
20
|
-
} from '../cardkit.js'
|
|
21
|
-
import { isCardRateLimitError, isCardTableLimitError } from '../card-errors.js'
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Mock client factory
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
type MockCall = { api: string; args: any }
|
|
28
|
-
|
|
29
|
-
function makeMockClient(responses: Record<string, any>) {
|
|
30
|
-
const calls: MockCall[] = []
|
|
31
|
-
const recorder = (api: string, resp: any) => async (args: any) => {
|
|
32
|
-
calls.push({ api, args })
|
|
33
|
-
if (typeof resp === 'function') {
|
|
34
|
-
return resp(args)
|
|
35
|
-
}
|
|
36
|
-
return resp
|
|
37
|
-
}
|
|
38
|
-
const client: any = {
|
|
39
|
-
cardkit: {
|
|
40
|
-
v1: {
|
|
41
|
-
card: {
|
|
42
|
-
create: recorder('cardkit.v1.card.create', responses['card.create']),
|
|
43
|
-
settings: recorder('cardkit.v1.card.settings', responses['card.settings']),
|
|
44
|
-
update: recorder('cardkit.v1.card.update', responses['card.update']),
|
|
45
|
-
},
|
|
46
|
-
cardElement: {
|
|
47
|
-
content: recorder(
|
|
48
|
-
'cardkit.v1.cardElement.content',
|
|
49
|
-
responses['cardElement.content'],
|
|
50
|
-
),
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
im: {
|
|
55
|
-
message: {
|
|
56
|
-
create: recorder('im.message.create', responses['im.message.create']),
|
|
57
|
-
reply: recorder('im.message.reply', responses['im.message.reply']),
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
return { client, calls }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// createCardEntity
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
|
|
68
|
-
describe('createCardEntity', () => {
|
|
69
|
-
it('构造 card_json payload 并返回 card_id', async () => {
|
|
70
|
-
const { client, calls } = makeMockClient({
|
|
71
|
-
'card.create': {
|
|
72
|
-
code: 0,
|
|
73
|
-
data: { card_id: 'ck_abc_123' },
|
|
74
|
-
},
|
|
75
|
-
})
|
|
76
|
-
const card = { schema: '2.0', body: { elements: [] } }
|
|
77
|
-
const id = await createCardEntity(client, card)
|
|
78
|
-
|
|
79
|
-
expect(id).toBe('ck_abc_123')
|
|
80
|
-
expect(calls.length).toBe(1)
|
|
81
|
-
expect(calls[0]!.api).toBe('cardkit.v1.card.create')
|
|
82
|
-
expect(calls[0]!.args.data.type).toBe('card_json')
|
|
83
|
-
// data.data 应当是 card 的 JSON 字符串
|
|
84
|
-
expect(calls[0]!.args.data.data).toBe(JSON.stringify(card))
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('兼容顶层 card_id(某些 SDK 包装层)', async () => {
|
|
88
|
-
const { client } = makeMockClient({
|
|
89
|
-
'card.create': { code: 0, card_id: 'top_level_id' },
|
|
90
|
-
})
|
|
91
|
-
const id = await createCardEntity(client, {})
|
|
92
|
-
expect(id).toBe('top_level_id')
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('non-zero code 抛 CardKitApiError', async () => {
|
|
96
|
-
const { client } = makeMockClient({
|
|
97
|
-
'card.create': { code: 230099, msg: 'something failed' },
|
|
98
|
-
})
|
|
99
|
-
await expect(createCardEntity(client, {})).rejects.toThrow(CardKitApiError)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('code=0 但缺 card_id 抛错', async () => {
|
|
103
|
-
const { client } = makeMockClient({
|
|
104
|
-
'card.create': { code: 0, data: {} },
|
|
105
|
-
})
|
|
106
|
-
await expect(createCardEntity(client, {})).rejects.toThrow(/missing card_id/)
|
|
107
|
-
})
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// sendCardAsMessage
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
describe('sendCardAsMessage', () => {
|
|
115
|
-
it('无 replyTo: 走 im.message.create 使用 chat_id', async () => {
|
|
116
|
-
const { client, calls } = makeMockClient({
|
|
117
|
-
'im.message.create': { data: { message_id: 'om_new_msg_1' } },
|
|
118
|
-
})
|
|
119
|
-
const mid = await sendCardAsMessage(client, 'oc_chat_123', 'ck_id_xyz')
|
|
120
|
-
expect(mid).toBe('om_new_msg_1')
|
|
121
|
-
expect(calls.length).toBe(1)
|
|
122
|
-
expect(calls[0]!.api).toBe('im.message.create')
|
|
123
|
-
expect(calls[0]!.args.params.receive_id_type).toBe('chat_id')
|
|
124
|
-
expect(calls[0]!.args.data.receive_id).toBe('oc_chat_123')
|
|
125
|
-
expect(calls[0]!.args.data.msg_type).toBe('interactive')
|
|
126
|
-
// content 格式: {"type":"card","data":{"card_id":"xxx"}}
|
|
127
|
-
const parsed = JSON.parse(calls[0]!.args.data.content)
|
|
128
|
-
expect(parsed).toEqual({ type: 'card', data: { card_id: 'ck_id_xyz' } })
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('有 replyTo: 走 im.message.reply', async () => {
|
|
132
|
-
const { client, calls } = makeMockClient({
|
|
133
|
-
'im.message.reply': { data: { message_id: 'om_reply_1' } },
|
|
134
|
-
})
|
|
135
|
-
const mid = await sendCardAsMessage(client, 'oc_chat_123', 'ck_id_xyz', 'om_parent')
|
|
136
|
-
expect(mid).toBe('om_reply_1')
|
|
137
|
-
expect(calls.length).toBe(1)
|
|
138
|
-
expect(calls[0]!.api).toBe('im.message.reply')
|
|
139
|
-
expect(calls[0]!.args.path.message_id).toBe('om_parent')
|
|
140
|
-
const parsed = JSON.parse(calls[0]!.args.data.content)
|
|
141
|
-
expect(parsed.data.card_id).toBe('ck_id_xyz')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('缺 message_id 抛错', async () => {
|
|
145
|
-
const { client } = makeMockClient({
|
|
146
|
-
'im.message.create': { data: {} },
|
|
147
|
-
})
|
|
148
|
-
await expect(sendCardAsMessage(client, 'c', 'ck')).rejects.toThrow(
|
|
149
|
-
/missing message_id/,
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
// streamCardContent
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
|
|
158
|
-
describe('streamCardContent', () => {
|
|
159
|
-
it('构造 content + sequence payload,path 包含 card_id + element_id', async () => {
|
|
160
|
-
const { client, calls } = makeMockClient({
|
|
161
|
-
'cardElement.content': { code: 0 },
|
|
162
|
-
})
|
|
163
|
-
await streamCardContent(client, 'ck_abc', STREAMING_ELEMENT_ID, 'hello', 42)
|
|
164
|
-
|
|
165
|
-
expect(calls.length).toBe(1)
|
|
166
|
-
expect(calls[0]!.api).toBe('cardkit.v1.cardElement.content')
|
|
167
|
-
expect(calls[0]!.args.data).toEqual({ content: 'hello', sequence: 42 })
|
|
168
|
-
expect(calls[0]!.args.path).toEqual({
|
|
169
|
-
card_id: 'ck_abc',
|
|
170
|
-
element_id: STREAMING_ELEMENT_ID,
|
|
171
|
-
})
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('STREAMING_ELEMENT_ID 常量 = "streaming_content"', () => {
|
|
175
|
-
expect(STREAMING_ELEMENT_ID).toBe('streaming_content')
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
it('230020 响应可被 isCardRateLimitError 识别', async () => {
|
|
179
|
-
const { client } = makeMockClient({
|
|
180
|
-
'cardElement.content': { code: 230020, msg: 'rate limited' },
|
|
181
|
-
})
|
|
182
|
-
try {
|
|
183
|
-
await streamCardContent(client, 'ck', 'el', 'x', 1)
|
|
184
|
-
expect('should have thrown').toBe('but did not')
|
|
185
|
-
} catch (err) {
|
|
186
|
-
expect(err).toBeInstanceOf(CardKitApiError)
|
|
187
|
-
expect(isCardRateLimitError(err)).toBe(true)
|
|
188
|
-
}
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('230099 + table limit msg 可被 isCardTableLimitError 识别', async () => {
|
|
192
|
-
const { client } = makeMockClient({
|
|
193
|
-
'cardElement.content': {
|
|
194
|
-
code: 230099,
|
|
195
|
-
msg: 'Failed to create card content, ext=ErrCode: 11310; ErrMsg: card table number over limit; ErrorValue: table; ',
|
|
196
|
-
},
|
|
197
|
-
})
|
|
198
|
-
try {
|
|
199
|
-
await streamCardContent(client, 'ck', 'el', 'x', 1)
|
|
200
|
-
expect('should have thrown').toBe('but did not')
|
|
201
|
-
} catch (err) {
|
|
202
|
-
expect(err).toBeInstanceOf(CardKitApiError)
|
|
203
|
-
expect(isCardTableLimitError(err)).toBe(true)
|
|
204
|
-
}
|
|
205
|
-
})
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
// ---------------------------------------------------------------------------
|
|
209
|
-
// setCardStreamingMode
|
|
210
|
-
// ---------------------------------------------------------------------------
|
|
211
|
-
|
|
212
|
-
describe('setCardStreamingMode', () => {
|
|
213
|
-
it('streaming_mode=false + sequence 正确传递', async () => {
|
|
214
|
-
const { client, calls } = makeMockClient({
|
|
215
|
-
'card.settings': { code: 0 },
|
|
216
|
-
})
|
|
217
|
-
await setCardStreamingMode(client, 'ck_xxx', false, 99)
|
|
218
|
-
|
|
219
|
-
expect(calls.length).toBe(1)
|
|
220
|
-
expect(calls[0]!.api).toBe('cardkit.v1.card.settings')
|
|
221
|
-
expect(calls[0]!.args.path).toEqual({ card_id: 'ck_xxx' })
|
|
222
|
-
expect(calls[0]!.args.data.sequence).toBe(99)
|
|
223
|
-
// settings 是 JSON 字符串
|
|
224
|
-
const settings = JSON.parse(calls[0]!.args.data.settings)
|
|
225
|
-
expect(settings).toEqual({ streaming_mode: false })
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('streaming_mode=true 也能工作', async () => {
|
|
229
|
-
const { client, calls } = makeMockClient({
|
|
230
|
-
'card.settings': { code: 0 },
|
|
231
|
-
})
|
|
232
|
-
await setCardStreamingMode(client, 'ck', true, 1)
|
|
233
|
-
const settings = JSON.parse(calls[0]!.args.data.settings)
|
|
234
|
-
expect(settings).toEqual({ streaming_mode: true })
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
// ---------------------------------------------------------------------------
|
|
239
|
-
// updateCardKitCard
|
|
240
|
-
// ---------------------------------------------------------------------------
|
|
241
|
-
|
|
242
|
-
describe('updateCardKitCard', () => {
|
|
243
|
-
it('把 card 包装成 card_json payload + sequence', async () => {
|
|
244
|
-
const { client, calls } = makeMockClient({
|
|
245
|
-
'card.update': { code: 0 },
|
|
246
|
-
})
|
|
247
|
-
const card = { schema: '2.0', body: { elements: [{ tag: 'markdown', content: 'done' }] } }
|
|
248
|
-
await updateCardKitCard(client, 'ck_final', card, 100)
|
|
249
|
-
|
|
250
|
-
expect(calls.length).toBe(1)
|
|
251
|
-
expect(calls[0]!.api).toBe('cardkit.v1.card.update')
|
|
252
|
-
expect(calls[0]!.args.path).toEqual({ card_id: 'ck_final' })
|
|
253
|
-
expect(calls[0]!.args.data.sequence).toBe(100)
|
|
254
|
-
expect(calls[0]!.args.data.card.type).toBe('card_json')
|
|
255
|
-
expect(calls[0]!.args.data.card.data).toBe(JSON.stringify(card))
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
it('非零 code 抛 CardKitApiError', async () => {
|
|
259
|
-
const { client } = makeMockClient({
|
|
260
|
-
'card.update': { code: -1, msg: 'bad card' },
|
|
261
|
-
})
|
|
262
|
-
await expect(updateCardKitCard(client, 'ck', {}, 1)).rejects.toThrow(CardKitApiError)
|
|
263
|
-
})
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
// ---------------------------------------------------------------------------
|
|
267
|
-
// CardKitApiError
|
|
268
|
-
// ---------------------------------------------------------------------------
|
|
269
|
-
|
|
270
|
-
describe('CardKitApiError', () => {
|
|
271
|
-
it('携带 code 和 msg,可被 parseCardApiError 识别', () => {
|
|
272
|
-
const err = new CardKitApiError({
|
|
273
|
-
api: 'card.update',
|
|
274
|
-
code: 230020,
|
|
275
|
-
msg: 'rate limited',
|
|
276
|
-
context: 'seq=5',
|
|
277
|
-
})
|
|
278
|
-
expect(err.code).toBe(230020)
|
|
279
|
-
expect(err.msg).toBe('rate limited')
|
|
280
|
-
expect(err.name).toBe('CardKitApiError')
|
|
281
|
-
expect(isCardRateLimitError(err)).toBe(true)
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
it('消息包含 api 名和 context', () => {
|
|
285
|
-
const err = new CardKitApiError({
|
|
286
|
-
api: 'cardElement.content',
|
|
287
|
-
code: 230099,
|
|
288
|
-
msg: 'oops',
|
|
289
|
-
context: 'seq=3 len=100',
|
|
290
|
-
})
|
|
291
|
-
expect(err.message).toContain('cardElement.content')
|
|
292
|
-
expect(err.message).toContain('230099')
|
|
293
|
-
expect(err.message).toContain('seq=3 len=100')
|
|
294
|
-
})
|
|
295
|
-
})
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'bun:test'
|
|
2
|
-
import { extractInboundPayload } from '../extract-payload.js'
|
|
3
|
-
|
|
4
|
-
describe('extractInboundPayload', () => {
|
|
5
|
-
it('pulls text out of a text message', () => {
|
|
6
|
-
const result = extractInboundPayload(
|
|
7
|
-
JSON.stringify({ text: 'hello world' }),
|
|
8
|
-
'text',
|
|
9
|
-
)
|
|
10
|
-
expect(result.text).toBe('hello world')
|
|
11
|
-
expect(result.pendingDownloads).toEqual([])
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('pulls text out of a post (rich text) message', () => {
|
|
15
|
-
const content = JSON.stringify({
|
|
16
|
-
zh_cn: {
|
|
17
|
-
content: [[{ tag: 'text', text: 'hi ' }, { tag: 'text', text: 'there' }]],
|
|
18
|
-
},
|
|
19
|
-
})
|
|
20
|
-
const result = extractInboundPayload(content, 'post')
|
|
21
|
-
expect(result.text).toBe('hi there')
|
|
22
|
-
expect(result.pendingDownloads).toEqual([])
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('identifies an image message as a pending image download', () => {
|
|
26
|
-
const content = JSON.stringify({ image_key: 'img_key_abc' })
|
|
27
|
-
const result = extractInboundPayload(content, 'image')
|
|
28
|
-
expect(result.text).toBe('')
|
|
29
|
-
expect(result.pendingDownloads).toEqual([
|
|
30
|
-
{ kind: 'image', fileKey: 'img_key_abc' },
|
|
31
|
-
])
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('identifies a file message as a pending file download with file_name', () => {
|
|
35
|
-
const content = JSON.stringify({
|
|
36
|
-
file_key: 'file_key_xyz',
|
|
37
|
-
file_name: 'spec.pdf',
|
|
38
|
-
})
|
|
39
|
-
const result = extractInboundPayload(content, 'file')
|
|
40
|
-
expect(result.pendingDownloads).toEqual([
|
|
41
|
-
{ kind: 'file', fileKey: 'file_key_xyz', fileName: 'spec.pdf' },
|
|
42
|
-
])
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('identifies file_archive the same way as file', () => {
|
|
46
|
-
const content = JSON.stringify({ file_key: 'fk1', file_name: 'x.zip' })
|
|
47
|
-
const result = extractInboundPayload(content, 'file_archive')
|
|
48
|
-
expect(result.pendingDownloads).toEqual([
|
|
49
|
-
{ kind: 'file', fileKey: 'fk1', fileName: 'x.zip' },
|
|
50
|
-
])
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('extracts img + file elements from a post message', () => {
|
|
54
|
-
const content = JSON.stringify({
|
|
55
|
-
zh_cn: {
|
|
56
|
-
content: [
|
|
57
|
-
[{ tag: 'text', text: 'look: ' }],
|
|
58
|
-
[{ tag: 'img', image_key: 'img_post_1' }],
|
|
59
|
-
[{ tag: 'text', text: ' and ' }],
|
|
60
|
-
[{ tag: 'file', file_key: 'file_post_1', file_name: 'note.txt' }],
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
const result = extractInboundPayload(content, 'post')
|
|
65
|
-
expect(result.text).toBe('look: and ')
|
|
66
|
-
expect(result.pendingDownloads).toEqual([
|
|
67
|
-
{ kind: 'image', fileKey: 'img_post_1' },
|
|
68
|
-
{ kind: 'file', fileKey: 'file_post_1', fileName: 'note.txt' },
|
|
69
|
-
])
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('returns empty on malformed JSON', () => {
|
|
73
|
-
const result = extractInboundPayload('not json', 'text')
|
|
74
|
-
expect(result.text).toBe('')
|
|
75
|
-
expect(result.pendingDownloads).toEqual([])
|
|
76
|
-
})
|
|
77
|
-
})
|