chz-telegram-bot 0.7.13 → 0.7.15
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/dist/dtos/propertyProviderSets.d.ts +0 -1
- package/dist/dtos/propertyProviderSets.d.ts.map +1 -1
- package/dist/entities/actions/commandAction.d.ts +1 -1
- package/dist/entities/actions/commandAction.d.ts.map +1 -1
- package/dist/entities/actions/commandAction.js +2 -2
- package/dist/helpers/builders/commandActionBuilder.d.ts.map +1 -1
- package/dist/helpers/builders/commandActionBuilder.js +2 -3
- package/package.json +4 -1
- package/eslint.config.ts +0 -62
- package/src/builtin/helpAction.ts +0 -17
- package/src/dtos/chatHistoryMessage.ts +0 -22
- package/src/dtos/chatInfo.ts +0 -12
- package/src/dtos/commandTriggerCheckResult.ts +0 -40
- package/src/dtos/cooldownInfo.ts +0 -10
- package/src/dtos/incomingMessage.ts +0 -71
- package/src/dtos/incomingQuery.ts +0 -14
- package/src/dtos/messageInfo.ts +0 -15
- package/src/dtos/propertyProviderSets.ts +0 -21
- package/src/dtos/replyInfo.ts +0 -9
- package/src/dtos/responses/delay.ts +0 -28
- package/src/dtos/responses/imageMessage.ts +0 -41
- package/src/dtos/responses/inlineQueryResponse.ts +0 -26
- package/src/dtos/responses/reaction.ts +0 -30
- package/src/dtos/responses/textMessage.ts +0 -44
- package/src/dtos/responses/unpin.ts +0 -27
- package/src/dtos/responses/videoMessage.ts +0 -41
- package/src/dtos/userInfo.ts +0 -8
- package/src/entities/actions/commandAction.ts +0 -275
- package/src/entities/actions/inlineQueryAction.ts +0 -83
- package/src/entities/actions/replyCaptureAction.ts +0 -110
- package/src/entities/actions/scheduledAction.ts +0 -182
- package/src/entities/botInstance.ts +0 -92
- package/src/entities/cachedStateFactory.ts +0 -14
- package/src/entities/context/baseContext.ts +0 -111
- package/src/entities/context/chatContext.ts +0 -135
- package/src/entities/context/inlineQueryContext.ts +0 -63
- package/src/entities/context/messageContext.ts +0 -250
- package/src/entities/context/replyContext.ts +0 -260
- package/src/entities/states/actionStateBase.ts +0 -6
- package/src/entities/taskRecord.ts +0 -11
- package/src/helpers/builders/commandActionBuilder.ts +0 -214
- package/src/helpers/builders/inlineQueryActionBuilder.ts +0 -71
- package/src/helpers/builders/scheduledActionBuilder.ts +0 -143
- package/src/helpers/mapUtils.ts +0 -28
- package/src/helpers/noop.ts +0 -20
- package/src/helpers/objectFromEntries.ts +0 -7
- package/src/helpers/timeConvertions.ts +0 -13
- package/src/helpers/toArray.ts +0 -3
- package/src/helpers/traceFactory.ts +0 -11
- package/src/index.ts +0 -33
- package/src/main.ts +0 -76
- package/src/services/actionProcessingService.ts +0 -125
- package/src/services/actionProcessors/baseProcessor.ts +0 -67
- package/src/services/actionProcessors/commandActionProcessor.ts +0 -231
- package/src/services/actionProcessors/inlineQueryActionProcessor.ts +0 -165
- package/src/services/actionProcessors/scheduledActionProcessor.ts +0 -136
- package/src/services/jsonFileStorage.ts +0 -181
- package/src/services/nodeTimeoutScheduler.ts +0 -79
- package/src/services/responseProcessingQueue.ts +0 -57
- package/src/services/telegramApi.ts +0 -278
- package/src/types/action.ts +0 -15
- package/src/types/actionState.ts +0 -4
- package/src/types/cachedValueAccessor.ts +0 -1
- package/src/types/capture.ts +0 -33
- package/src/types/commandCondition.ts +0 -9
- package/src/types/commandTrigger.ts +0 -1
- package/src/types/events.ts +0 -286
- package/src/types/externalAliases.ts +0 -18
- package/src/types/handlers.ts +0 -26
- package/src/types/inputFile.ts +0 -4
- package/src/types/messageSendingOptions.ts +0 -10
- package/src/types/messageTypes.ts +0 -21
- package/src/types/propertyProvider.ts +0 -14
- package/src/types/response.ts +0 -51
- package/src/types/scheduler.ts +0 -20
- package/src/types/storage.ts +0 -23
- package/src/types/timeValues.ts +0 -33
- package/src/types/trace.ts +0 -5
- package/tests/dtos/commandTriggerCheckResult.test.ts +0 -301
- package/tests/entities/actions/inlineQueryAction.test.ts +0 -359
- package/tests/entities/actions/replyCaptureAction.test.ts +0 -501
- package/tests/entities/cachedStateFactory.test.ts +0 -98
- package/tests/entities/context/chatContext.test.ts +0 -606
- package/tests/entities/context/messageContext.test.ts +0 -370
- package/tests/entities/states/actionStateBase.test.ts +0 -138
- package/tests/entities/taskRecord.test.ts +0 -195
- package/tests/helpers/mapUtils.test.ts +0 -163
- package/tests/helpers/timeConvertions.test.ts +0 -129
- package/tests/services/actionProcessors/baseActionProcessor.test.ts +0 -359
- package/tests/services/actionProcessors/commandActionProcessor.test.ts +0 -268
- package/tests/services/actionProcessors/inlineQueryActionProcessor.test.ts +0 -616
- package/tests/services/actionProcessors/processorTestHelpers.ts +0 -147
- package/tests/services/actionProcessors/scheduledActionProcessor.test.ts +0 -153
- package/tests/services/jsonFileStorage.test.ts +0 -927
- package/tests/services/nodeTimeoutScheduler.test.ts +0 -421
- package/tests/services/responseProcessingQueue.test.ts +0 -388
- package/tsconfig.build.json +0 -8
- package/tsconfig.json +0 -118
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import { getOrSetIfNotExists, getOrThrow } from '../../src/helpers/mapUtils';
|
|
3
|
-
|
|
4
|
-
describe('mapUtils', () => {
|
|
5
|
-
describe('getOrSetIfNotExists', () => {
|
|
6
|
-
test('should return existing value if key exists', () => {
|
|
7
|
-
const map = new Map<string, number>();
|
|
8
|
-
map.set('existing', 42);
|
|
9
|
-
|
|
10
|
-
const result = getOrSetIfNotExists(map, 'existing', 100);
|
|
11
|
-
|
|
12
|
-
expect(result).toBe(42);
|
|
13
|
-
expect(map.get('existing')).toBe(42);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('should set and return fallback if key does not exist', () => {
|
|
17
|
-
const map = new Map<string, number>();
|
|
18
|
-
|
|
19
|
-
const result = getOrSetIfNotExists(map, 'new-key', 100);
|
|
20
|
-
|
|
21
|
-
expect(result).toBe(100);
|
|
22
|
-
expect(map.get('new-key')).toBe(100);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('should not overwrite existing value', () => {
|
|
26
|
-
const map = new Map<string, string>();
|
|
27
|
-
map.set('key', 'original');
|
|
28
|
-
|
|
29
|
-
getOrSetIfNotExists(map, 'key', 'fallback');
|
|
30
|
-
|
|
31
|
-
expect(map.get('key')).toBe('original');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('should work with complex value types', () => {
|
|
35
|
-
const map = new Map<string, { id: number; name: string }>();
|
|
36
|
-
|
|
37
|
-
const fallback = { id: 1, name: 'test' };
|
|
38
|
-
const result = getOrSetIfNotExists(map, 'obj', fallback);
|
|
39
|
-
|
|
40
|
-
expect(result).toBe(fallback);
|
|
41
|
-
expect(map.get('obj')).toBe(fallback);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('should work with number keys', () => {
|
|
45
|
-
const map = new Map<number, string>();
|
|
46
|
-
|
|
47
|
-
const result = getOrSetIfNotExists(map, 123, 'value');
|
|
48
|
-
|
|
49
|
-
expect(result).toBe('value');
|
|
50
|
-
expect(map.get(123)).toBe('value');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('should handle falsy values correctly (except undefined)', () => {
|
|
54
|
-
const map = new Map<string, number>();
|
|
55
|
-
map.set('zero', 0);
|
|
56
|
-
|
|
57
|
-
// Note: current implementation treats 0 as falsy, so fallback is used
|
|
58
|
-
// This is a known edge case in the implementation
|
|
59
|
-
const result = getOrSetIfNotExists(map, 'zero', 999);
|
|
60
|
-
|
|
61
|
-
// Based on implementation: if (existingValue) - 0 is falsy
|
|
62
|
-
expect(result).toBe(999);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('should handle empty string value', () => {
|
|
66
|
-
const map = new Map<string, string>();
|
|
67
|
-
map.set('empty', '');
|
|
68
|
-
|
|
69
|
-
// Empty string is falsy, so fallback is used
|
|
70
|
-
const result = getOrSetIfNotExists(map, 'empty', 'fallback');
|
|
71
|
-
|
|
72
|
-
expect(result).toBe('fallback');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Real-world usage: JsonFileStorage uses this for file paths and locks
|
|
76
|
-
test('should work with Semaphore pattern (like JsonFileStorage)', () => {
|
|
77
|
-
// Simulated Semaphore-like object
|
|
78
|
-
class MockSemaphore {
|
|
79
|
-
constructor(readonly limit: number) {}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const locks = new Map<string, MockSemaphore>();
|
|
83
|
-
|
|
84
|
-
const lock1 = getOrSetIfNotExists(
|
|
85
|
-
locks,
|
|
86
|
-
'action:key',
|
|
87
|
-
new MockSemaphore(1)
|
|
88
|
-
);
|
|
89
|
-
expect(lock1.limit).toBe(1);
|
|
90
|
-
expect(locks.size).toBe(1);
|
|
91
|
-
|
|
92
|
-
// Second call should return same instance
|
|
93
|
-
const lock2 = getOrSetIfNotExists(
|
|
94
|
-
locks,
|
|
95
|
-
'action:key',
|
|
96
|
-
new MockSemaphore(5)
|
|
97
|
-
);
|
|
98
|
-
expect(lock2).toBe(lock1);
|
|
99
|
-
expect(lock2.limit).toBe(1); // Original limit, not 5
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('getOrThrow', () => {
|
|
104
|
-
test('should return value if key exists', () => {
|
|
105
|
-
const map = new Map<string, number>();
|
|
106
|
-
map.set('key', 42);
|
|
107
|
-
|
|
108
|
-
const result = getOrThrow(map, 'key');
|
|
109
|
-
|
|
110
|
-
expect(result).toBe(42);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('should throw default error if key does not exist', () => {
|
|
114
|
-
const map = new Map<string, number>();
|
|
115
|
-
|
|
116
|
-
expect(() => getOrThrow(map, 'missing')).toThrow(
|
|
117
|
-
'Key not found in collection'
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('should throw custom error message if provided', () => {
|
|
122
|
-
const map = new Map<string, number>();
|
|
123
|
-
|
|
124
|
-
expect(() =>
|
|
125
|
-
getOrThrow(map, 'missing', 'Custom error message')
|
|
126
|
-
).toThrow('Custom error message');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('should work with complex value types', () => {
|
|
130
|
-
const map = new Map<string, { data: string[] }>();
|
|
131
|
-
const value = { data: ['a', 'b', 'c'] };
|
|
132
|
-
map.set('complex', value);
|
|
133
|
-
|
|
134
|
-
const result = getOrThrow(map, 'complex');
|
|
135
|
-
|
|
136
|
-
expect(result).toBe(value);
|
|
137
|
-
expect(result.data).toEqual(['a', 'b', 'c']);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('should work with readonly collections', () => {
|
|
141
|
-
const map = new Map<string, number>();
|
|
142
|
-
map.set('key', 100);
|
|
143
|
-
|
|
144
|
-
// Cast to readonly-like interface
|
|
145
|
-
const readonlyMap: { get: (key: string) => number | undefined } =
|
|
146
|
-
map;
|
|
147
|
-
|
|
148
|
-
const result = getOrThrow(readonlyMap, 'key');
|
|
149
|
-
|
|
150
|
-
expect(result).toBe(100);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('should handle falsy values correctly (except undefined)', () => {
|
|
154
|
-
const map = new Map<string, number>();
|
|
155
|
-
map.set('zero', 0);
|
|
156
|
-
|
|
157
|
-
// Note: current implementation treats 0 as falsy, so it throws
|
|
158
|
-
expect(() => getOrThrow(map, 'zero')).toThrow(
|
|
159
|
-
'Key not found in collection'
|
|
160
|
-
);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
secondsToMilliseconds,
|
|
4
|
-
hoursToMilliseconds,
|
|
5
|
-
hoursToSeconds
|
|
6
|
-
} from '../../src/helpers/timeConvertions';
|
|
7
|
-
import { Hours, Milliseconds, Seconds } from '../../src/types/timeValues';
|
|
8
|
-
|
|
9
|
-
describe('timeConvertions', () => {
|
|
10
|
-
describe('secondsToMilliseconds', () => {
|
|
11
|
-
test('should convert 1 second to 1000 milliseconds', () => {
|
|
12
|
-
const result = secondsToMilliseconds(1 as Seconds);
|
|
13
|
-
expect(result).toBe(1000 as Milliseconds);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('should convert 0 seconds to 0 milliseconds', () => {
|
|
17
|
-
const result = secondsToMilliseconds(0 as Seconds);
|
|
18
|
-
expect(result).toBe(0 as Milliseconds);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('should convert fractional seconds', () => {
|
|
22
|
-
const result = secondsToMilliseconds(0.5 as Seconds);
|
|
23
|
-
expect(result).toBe(500 as Milliseconds);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('should convert large values', () => {
|
|
27
|
-
const result = secondsToMilliseconds(3600 as Seconds); // 1 hour in seconds
|
|
28
|
-
expect(result).toBe(3600000 as Milliseconds);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('should handle decimal precision', () => {
|
|
32
|
-
const result = secondsToMilliseconds(1.234 as Seconds);
|
|
33
|
-
expect(result).toBe(1234 as Milliseconds);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Real usage: ScheduledActionProcessor uses this for period conversion
|
|
37
|
-
test('should convert period for scheduled action processor', () => {
|
|
38
|
-
const periodInSeconds = 3600 as Seconds; // Default 1 hour
|
|
39
|
-
const result = secondsToMilliseconds(periodInSeconds);
|
|
40
|
-
expect(result).toBe(3600000 as Milliseconds);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('hoursToMilliseconds', () => {
|
|
45
|
-
test('should convert 1 hour to 3600000 milliseconds', () => {
|
|
46
|
-
const result = hoursToMilliseconds(1 as Hours);
|
|
47
|
-
expect(result).toBe(3600000 as Milliseconds);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('should convert 0 hours to 0 milliseconds', () => {
|
|
51
|
-
const result = hoursToMilliseconds(0 as Hours);
|
|
52
|
-
expect(result).toBe(0 as Milliseconds);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('should convert 24 hours to correct milliseconds', () => {
|
|
56
|
-
const result = hoursToMilliseconds(24 as Hours);
|
|
57
|
-
expect(result).toBe(86400000 as Milliseconds); // 24 * 60 * 60 * 1000
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('should convert fractional hours', () => {
|
|
61
|
-
const result = hoursToMilliseconds(0.5 as Hours);
|
|
62
|
-
expect(result).toBe(1800000 as Milliseconds); // 30 minutes
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('should convert quarter hour', () => {
|
|
66
|
-
const result = hoursToMilliseconds(0.25 as Hours);
|
|
67
|
-
expect(result).toBe(900000 as Milliseconds); // 15 minutes
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('hoursToSeconds', () => {
|
|
72
|
-
test('should convert 1 hour to 3600 seconds', () => {
|
|
73
|
-
const result = hoursToSeconds(1 as Hours);
|
|
74
|
-
expect(result).toBe(3600 as Seconds);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('should convert 0 hours to 0 seconds', () => {
|
|
78
|
-
const result = hoursToSeconds(0 as Hours);
|
|
79
|
-
expect(result).toBe(0 as Seconds);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('should convert 24 hours to correct seconds', () => {
|
|
83
|
-
const result = hoursToSeconds(24 as Hours);
|
|
84
|
-
expect(result).toBe(86400 as Seconds);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('should convert fractional hours', () => {
|
|
88
|
-
const result = hoursToSeconds(0.5 as Hours);
|
|
89
|
-
expect(result).toBe(1800 as Seconds); // 30 minutes
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Real usage: ActionProcessingService default scheduledPeriod
|
|
93
|
-
test('should convert default scheduled period', () => {
|
|
94
|
-
const defaultPeriod = hoursToSeconds(1 as Hours);
|
|
95
|
-
expect(defaultPeriod).toBe(3600 as Seconds);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('conversion consistency', () => {
|
|
100
|
-
test('hoursToMilliseconds should equal hoursToSeconds * 1000', () => {
|
|
101
|
-
const hours = 2 as Hours;
|
|
102
|
-
const viaMilliseconds = hoursToMilliseconds(hours);
|
|
103
|
-
const viaSeconds = secondsToMilliseconds(hoursToSeconds(hours));
|
|
104
|
-
|
|
105
|
-
expect(viaMilliseconds).toBe(viaSeconds);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('should maintain precision for common values', () => {
|
|
109
|
-
// Common scheduling intervals
|
|
110
|
-
expect(hoursToSeconds(1 as Hours)).toBe(3600 as Seconds);
|
|
111
|
-
expect(hoursToSeconds(6 as Hours)).toBe(21600 as Seconds);
|
|
112
|
-
expect(hoursToSeconds(12 as Hours)).toBe(43200 as Seconds);
|
|
113
|
-
expect(hoursToSeconds(24 as Hours)).toBe(86400 as Seconds);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('type safety', () => {
|
|
118
|
-
test('return types are correctly branded', () => {
|
|
119
|
-
const ms: Milliseconds = secondsToMilliseconds(1 as Seconds);
|
|
120
|
-
const s: Seconds = hoursToSeconds(1 as Hours);
|
|
121
|
-
const msFromHours: Milliseconds = hoursToMilliseconds(1 as Hours);
|
|
122
|
-
|
|
123
|
-
// These should compile and be usable
|
|
124
|
-
expect(typeof ms).toBe('number');
|
|
125
|
-
expect(typeof s).toBe('number');
|
|
126
|
-
expect(typeof msFromHours).toBe('number');
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
});
|
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, mock } from 'bun:test';
|
|
2
|
-
import { BotEventType, TypedEventEmitter } from '../../../src/types/events';
|
|
3
|
-
import { IScheduler } from '../../../src/types/scheduler';
|
|
4
|
-
import { IStorageClient } from '../../../src/types/storage';
|
|
5
|
-
import { ActionKey, IAction } from '../../../src/types/action';
|
|
6
|
-
import { BaseActionProcessor } from '../../../src/services/actionProcessors/baseProcessor';
|
|
7
|
-
import { BaseContextInternal } from '../../../src/entities/context/baseContext';
|
|
8
|
-
import { ChatInfo } from '../../../src/dtos/chatInfo';
|
|
9
|
-
import { TraceId } from '../../../src/types/trace';
|
|
10
|
-
import {
|
|
11
|
-
createMockStorage,
|
|
12
|
-
createMockScheduler,
|
|
13
|
-
createMockAction,
|
|
14
|
-
createMockTelegramApi,
|
|
15
|
-
createMockChatInfo,
|
|
16
|
-
createMockTraceId,
|
|
17
|
-
createMockTextResponse,
|
|
18
|
-
type MockTelegramApi
|
|
19
|
-
} from './processorTestHelpers';
|
|
20
|
-
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// Concrete implementation for testing abstract BaseActionProcessor
|
|
23
|
-
// =============================================================================
|
|
24
|
-
|
|
25
|
-
// Helper for async delay
|
|
26
|
-
const delay = (ms: number): Promise<void> =>
|
|
27
|
-
new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
|
-
|
|
29
|
-
// A minimal mock context that satisfies BaseContextInternal interface
|
|
30
|
-
class MockBaseContext extends BaseContextInternal<IAction> {
|
|
31
|
-
constructor(
|
|
32
|
-
storage: IStorageClient,
|
|
33
|
-
scheduler: IScheduler,
|
|
34
|
-
eventEmitter: TypedEventEmitter,
|
|
35
|
-
action: IAction = createMockAction('default-action'),
|
|
36
|
-
chatInfo: ChatInfo = new ChatInfo(12345, 'Test Chat', []),
|
|
37
|
-
traceId: TraceId = 'test-trace' as TraceId,
|
|
38
|
-
botName: string = 'TestBot'
|
|
39
|
-
) {
|
|
40
|
-
super(
|
|
41
|
-
storage,
|
|
42
|
-
scheduler,
|
|
43
|
-
eventEmitter,
|
|
44
|
-
action,
|
|
45
|
-
chatInfo,
|
|
46
|
-
traceId,
|
|
47
|
-
botName
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
class TestableBaseActionProcessor extends BaseActionProcessor {
|
|
53
|
-
// Expose protected members for testing
|
|
54
|
-
getStorage() {
|
|
55
|
-
return this.storage;
|
|
56
|
-
}
|
|
57
|
-
getScheduler() {
|
|
58
|
-
return this.scheduler;
|
|
59
|
-
}
|
|
60
|
-
getEventEmitter() {
|
|
61
|
-
return this.eventEmitter;
|
|
62
|
-
}
|
|
63
|
-
getBotName() {
|
|
64
|
-
return this.botName;
|
|
65
|
-
}
|
|
66
|
-
getApi() {
|
|
67
|
-
return this.api;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Public wrapper for executeAction
|
|
71
|
-
testExecuteAction(
|
|
72
|
-
action: IAction,
|
|
73
|
-
ctx: MockBaseContext,
|
|
74
|
-
errorHandler?: (error: Error, ctx: MockBaseContext) => void
|
|
75
|
-
) {
|
|
76
|
-
return this.executeAction(action, ctx, errorHandler);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// =============================================================================
|
|
81
|
-
// BaseActionProcessor Tests
|
|
82
|
-
// =============================================================================
|
|
83
|
-
|
|
84
|
-
describe('BaseActionProcessor', () => {
|
|
85
|
-
let processor: TestableBaseActionProcessor;
|
|
86
|
-
let eventEmitter: TypedEventEmitter;
|
|
87
|
-
let storage: IStorageClient;
|
|
88
|
-
let scheduler: IScheduler;
|
|
89
|
-
|
|
90
|
-
beforeEach(() => {
|
|
91
|
-
eventEmitter = new TypedEventEmitter();
|
|
92
|
-
storage = createMockStorage();
|
|
93
|
-
scheduler = createMockScheduler();
|
|
94
|
-
processor = new TestableBaseActionProcessor(
|
|
95
|
-
'test-bot',
|
|
96
|
-
storage,
|
|
97
|
-
scheduler,
|
|
98
|
-
eventEmitter
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('constructor', () => {
|
|
103
|
-
test('should store storage reference', () => {
|
|
104
|
-
expect(processor.getStorage()).toBe(storage);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('should store scheduler reference', () => {
|
|
108
|
-
expect(processor.getScheduler()).toBe(scheduler);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('should store eventEmitter reference', () => {
|
|
112
|
-
expect(processor.getEventEmitter()).toBe(eventEmitter);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('should store botName', () => {
|
|
116
|
-
expect(processor.getBotName()).toBe('test-bot');
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('initializeDependencies', () => {
|
|
121
|
-
test('should store api reference', () => {
|
|
122
|
-
const mockApi = createMockTelegramApi();
|
|
123
|
-
processor.initializeDependencies(mockApi);
|
|
124
|
-
expect(processor.getApi()).toBe(mockApi);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('executeAction', () => {
|
|
129
|
-
let mockApi: MockTelegramApi;
|
|
130
|
-
let mockContext: MockBaseContext;
|
|
131
|
-
|
|
132
|
-
beforeEach(() => {
|
|
133
|
-
mockApi = createMockTelegramApi();
|
|
134
|
-
processor.initializeDependencies(mockApi);
|
|
135
|
-
const action = createMockAction('ctx-action');
|
|
136
|
-
mockContext = new MockBaseContext(
|
|
137
|
-
storage,
|
|
138
|
-
scheduler,
|
|
139
|
-
eventEmitter,
|
|
140
|
-
action
|
|
141
|
-
);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test('should call action.exec with context', async () => {
|
|
145
|
-
const action = createMockAction('test-action');
|
|
146
|
-
|
|
147
|
-
await processor.testExecuteAction(action, mockContext);
|
|
148
|
-
|
|
149
|
-
expect(action.getExecCallCount()).toBe(1);
|
|
150
|
-
expect(action.getExecLastArgs()).toEqual([mockContext]);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('should enqueue responses from action', async () => {
|
|
154
|
-
const chatInfo = createMockChatInfo();
|
|
155
|
-
const traceId = createMockTraceId();
|
|
156
|
-
const action = createMockAction('test-action');
|
|
157
|
-
const response = createMockTextResponse(chatInfo, traceId, action);
|
|
158
|
-
action.exec = mock(() => Promise.resolve([response]));
|
|
159
|
-
|
|
160
|
-
await processor.testExecuteAction(action, mockContext);
|
|
161
|
-
|
|
162
|
-
expect(mockApi.getEnqueueCallCount()).toBe(1);
|
|
163
|
-
expect(mockApi.getEnqueueLastArgs()).toEqual([response]);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('should handle action execution', async () => {
|
|
167
|
-
const action = createMockAction('test-action');
|
|
168
|
-
|
|
169
|
-
await processor.testExecuteAction(action, mockContext);
|
|
170
|
-
|
|
171
|
-
// Verify execution completed successfully
|
|
172
|
-
expect(mockApi.getEnqueueCallCount()).toBe(1);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('should call custom error handler on error', async () => {
|
|
176
|
-
const action = createMockAction('test-action');
|
|
177
|
-
const testError = new Error('Test error');
|
|
178
|
-
action.exec = mock(() => Promise.reject(testError));
|
|
179
|
-
|
|
180
|
-
const errorHandler = mock(() => {
|
|
181
|
-
/* no-op */
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
await processor.testExecuteAction(
|
|
185
|
-
action,
|
|
186
|
-
mockContext,
|
|
187
|
-
errorHandler
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
expect(errorHandler).toHaveBeenCalledWith(testError, mockContext);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test('should emit error event when no custom handler provided', async () => {
|
|
194
|
-
// BUG: defaultErrorHandler loses 'this' context when called via
|
|
195
|
-
// (errorHandler ?? this.defaultErrorHandler)(error, ctx)
|
|
196
|
-
// This test expects the correct behavior - fix by binding the method
|
|
197
|
-
const action = createMockAction('test-action');
|
|
198
|
-
const testError = new Error('Test error message');
|
|
199
|
-
testError.name = 'TestError';
|
|
200
|
-
action.exec = mock(() => Promise.reject(testError));
|
|
201
|
-
|
|
202
|
-
const errorEvents: unknown[] = [];
|
|
203
|
-
eventEmitter.on(BotEventType.error, (_ts, data) => {
|
|
204
|
-
errorEvents.push(data);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Suppress console.error for cleaner test output
|
|
208
|
-
const originalConsoleError = console.error;
|
|
209
|
-
console.error = () => {};
|
|
210
|
-
|
|
211
|
-
await processor.testExecuteAction(action, mockContext);
|
|
212
|
-
|
|
213
|
-
console.error = originalConsoleError;
|
|
214
|
-
|
|
215
|
-
expect(errorEvents.length).toBe(1);
|
|
216
|
-
expect(errorEvents[0]).toMatchObject({
|
|
217
|
-
error: testError,
|
|
218
|
-
traceId: 'test-trace'
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test('should log error to console with default handler', async () => {
|
|
223
|
-
// BUG: defaultErrorHandler loses 'this' context - needs binding fix
|
|
224
|
-
const action = createMockAction('test-action');
|
|
225
|
-
const testError = new Error('Console log test');
|
|
226
|
-
action.exec = mock(() => Promise.reject(testError));
|
|
227
|
-
|
|
228
|
-
const consoleErrors: unknown[] = [];
|
|
229
|
-
const originalConsoleError = console.error;
|
|
230
|
-
console.error = (...args: unknown[]) => {
|
|
231
|
-
consoleErrors.push(args[0]);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
await processor.testExecuteAction(action, mockContext);
|
|
235
|
-
|
|
236
|
-
console.error = originalConsoleError;
|
|
237
|
-
|
|
238
|
-
expect(consoleErrors.length).toBe(1);
|
|
239
|
-
expect(consoleErrors[0]).toBe(testError);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('error handling edge cases', () => {
|
|
244
|
-
test('should handle async errors correctly with default handler', async () => {
|
|
245
|
-
// BUG: defaultErrorHandler loses 'this' context - needs binding fix
|
|
246
|
-
const localEventEmitter = new TypedEventEmitter();
|
|
247
|
-
const localStorage = createMockStorage();
|
|
248
|
-
const localScheduler = createMockScheduler();
|
|
249
|
-
const localProcessor = new TestableBaseActionProcessor(
|
|
250
|
-
'error-bot',
|
|
251
|
-
localStorage,
|
|
252
|
-
localScheduler,
|
|
253
|
-
localEventEmitter
|
|
254
|
-
);
|
|
255
|
-
const mockApi = createMockTelegramApi();
|
|
256
|
-
localProcessor.initializeDependencies(mockApi);
|
|
257
|
-
|
|
258
|
-
const asyncError = new Error('Async failure');
|
|
259
|
-
asyncError.name = 'AsyncError';
|
|
260
|
-
|
|
261
|
-
const action: IAction = {
|
|
262
|
-
key: 'error-action' as ActionKey,
|
|
263
|
-
exec: mock(async () => {
|
|
264
|
-
await delay(10);
|
|
265
|
-
throw asyncError;
|
|
266
|
-
})
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const errorEvents: unknown[] = [];
|
|
270
|
-
localEventEmitter.on(BotEventType.error, (_ts, data) => {
|
|
271
|
-
errorEvents.push(data);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
const originalConsoleError = console.error;
|
|
275
|
-
console.error = () => {
|
|
276
|
-
/* no-op */
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
const ctx = new MockBaseContext(
|
|
280
|
-
localStorage,
|
|
281
|
-
localScheduler,
|
|
282
|
-
localEventEmitter,
|
|
283
|
-
action
|
|
284
|
-
);
|
|
285
|
-
await localProcessor.testExecuteAction(action, ctx);
|
|
286
|
-
|
|
287
|
-
console.error = originalConsoleError;
|
|
288
|
-
|
|
289
|
-
expect(errorEvents.length).toBe(1);
|
|
290
|
-
expect(errorEvents[0]).toMatchObject({
|
|
291
|
-
error: asyncError,
|
|
292
|
-
traceId: 'test-trace'
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
test('should handle empty response array', async () => {
|
|
297
|
-
const localStorage = createMockStorage();
|
|
298
|
-
const localScheduler = createMockScheduler();
|
|
299
|
-
const localEventEmitter = new TypedEventEmitter();
|
|
300
|
-
const localProcessor = new TestableBaseActionProcessor(
|
|
301
|
-
'empty-bot',
|
|
302
|
-
localStorage,
|
|
303
|
-
localScheduler,
|
|
304
|
-
localEventEmitter
|
|
305
|
-
);
|
|
306
|
-
const localMockApi = createMockTelegramApi();
|
|
307
|
-
localProcessor.initializeDependencies(localMockApi);
|
|
308
|
-
|
|
309
|
-
const action = createMockAction('empty-action', []);
|
|
310
|
-
const ctx = new MockBaseContext(
|
|
311
|
-
localStorage,
|
|
312
|
-
localScheduler,
|
|
313
|
-
localEventEmitter,
|
|
314
|
-
action
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
await localProcessor.testExecuteAction(action, ctx);
|
|
318
|
-
|
|
319
|
-
expect(localMockApi.getEnqueueCallCount()).toBe(1);
|
|
320
|
-
expect(localMockApi.getEnqueueLastArgs()).toEqual([]);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test('should handle multiple responses', async () => {
|
|
324
|
-
const localStorage = createMockStorage();
|
|
325
|
-
const localScheduler = createMockScheduler();
|
|
326
|
-
const localEventEmitter = new TypedEventEmitter();
|
|
327
|
-
const localProcessor = new TestableBaseActionProcessor(
|
|
328
|
-
'multi-response-bot',
|
|
329
|
-
localStorage,
|
|
330
|
-
localScheduler,
|
|
331
|
-
localEventEmitter
|
|
332
|
-
);
|
|
333
|
-
const localMockApi = createMockTelegramApi();
|
|
334
|
-
localProcessor.initializeDependencies(localMockApi);
|
|
335
|
-
|
|
336
|
-
const chatInfo = createMockChatInfo();
|
|
337
|
-
const traceId = createMockTraceId();
|
|
338
|
-
const action = createMockAction('multi-action');
|
|
339
|
-
|
|
340
|
-
const responses = [
|
|
341
|
-
createMockTextResponse(chatInfo, traceId, action),
|
|
342
|
-
createMockTextResponse(chatInfo, traceId, action),
|
|
343
|
-
createMockTextResponse(chatInfo, traceId, action)
|
|
344
|
-
];
|
|
345
|
-
action.exec = mock(() => Promise.resolve(responses));
|
|
346
|
-
|
|
347
|
-
const ctx = new MockBaseContext(
|
|
348
|
-
localStorage,
|
|
349
|
-
localScheduler,
|
|
350
|
-
localEventEmitter,
|
|
351
|
-
action
|
|
352
|
-
);
|
|
353
|
-
await localProcessor.testExecuteAction(action, ctx);
|
|
354
|
-
|
|
355
|
-
expect(localMockApi.getEnqueueCallCount()).toBe(1);
|
|
356
|
-
expect(localMockApi.getEnqueueLastArgs()).toEqual(responses);
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
});
|