@seeka-labs/cli-apps 3.8.10 → 3.9.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/README.md +286 -0
- package/dist/index.cjs +821 -612
- package/dist/init-template.zip +0 -0
- package/package.json +5 -3
- package/dist/ai-context/common/README.md +0 -110
- package/dist/ai-context/common/architecture/overview.md +0 -140
- package/dist/ai-context/common/guides/common-patterns.md +0 -471
- package/dist/ai-context/common/guides/data-flow.md +0 -455
- package/dist/ai-context/common/guides/getting-started.md +0 -349
- package/dist/ai-context/common/guides/testing.md +0 -592
- package/dist/ai-context/common/reference/cli-commands.md +0 -231
- package/dist/ai-context/common/reference/config-schema.md +0 -345
- package/dist/ai-context/common/reference/sdk-api.md +0 -445
- package/dist/ai-context/public/architecture/app-structure.md +0 -385
- package/dist/index.cjs.map +0 -7
- package/dist/init-template/.github/workflows/deploy-azurefunc.yml +0 -198
- package/dist/init-template/.gitlab-ci.yml +0 -67
- package/dist/init-template/.nvmrc +0 -1
- package/dist/init-template/AGENTS.md +0 -99
- package/dist/init-template/README.azurefunc.md +0 -238
- package/dist/init-template/app/.eslintrc.cjs +0 -13
- package/dist/init-template/app/browser/README.md +0 -1
- package/dist/init-template/app/browser/package.json +0 -36
- package/dist/init-template/app/browser/scripts/esbuild/build-browser-plugin.mjs +0 -130
- package/dist/init-template/app/browser/scripts/esbuild/plugins/importAsGlobals.mjs +0 -39
- package/dist/init-template/app/browser/src/browser.ts +0 -12
- package/dist/init-template/app/browser/src/plugin/index.ts +0 -61
- package/dist/init-template/app/browser/tsconfig.json +0 -34
- package/dist/init-template/app/lib/package.json +0 -46
- package/dist/init-template/app/lib/src/index.ts +0 -4
- package/dist/init-template/app/lib/src/models/index.ts +0 -29
- package/dist/init-template/app/lib/src/validation/index.ts +0 -14
- package/dist/init-template/app/lib/tsconfig.json +0 -22
- package/dist/init-template/app/server-azurefunc/.eslintrc.cjs +0 -4
- package/dist/init-template/app/server-azurefunc/.funcignore +0 -20
- package/dist/init-template/app/server-azurefunc/README.md +0 -105
- package/dist/init-template/app/server-azurefunc/host.json +0 -31
- package/dist/init-template/app/server-azurefunc/local.settings.template.json +0 -30
- package/dist/init-template/app/server-azurefunc/package.json +0 -68
- package/dist/init-template/app/server-azurefunc/scripts/build.mjs +0 -67
- package/dist/init-template/app/server-azurefunc/scripts/dev-queue-setup.js +0 -55
- package/dist/init-template/app/server-azurefunc/src/app/api/router.ts +0 -15
- package/dist/init-template/app/server-azurefunc/src/app/api/routes/getInstallationSettings.ts +0 -13
- package/dist/init-template/app/server-azurefunc/src/app/api/routes/setInstallationSettings.ts +0 -35
- package/dist/init-template/app/server-azurefunc/src/app/jobs/index.ts +0 -61
- package/dist/init-template/app/server-azurefunc/src/app/logging/index.ts +0 -4
- package/dist/init-template/app/server-azurefunc/src/app/models/index.ts +0 -12
- package/dist/init-template/app/server-azurefunc/src/app/services/activites.ts +0 -8
- package/dist/init-template/app/server-azurefunc/src/functions/healthCheck.ts +0 -19
- package/dist/init-template/app/server-azurefunc/src/functions/seekaAppWebhook.ts +0 -204
- package/dist/init-template/app/server-azurefunc/src/functions/trackActivityQueueHandler.ts +0 -48
- package/dist/init-template/app/server-azurefunc/src/functions/ui.ts +0 -49
- package/dist/init-template/app/server-azurefunc/tsconfig.json +0 -24
- package/dist/init-template/app/ui/README.md +0 -40
- package/dist/init-template/app/ui/index.html +0 -21
- package/dist/init-template/app/ui/package.json +0 -72
- package/dist/init-template/app/ui/public/favicon.ico +0 -0
- package/dist/init-template/app/ui/scripts/copy-output.mjs +0 -30
- package/dist/init-template/app/ui/src/App.tsx +0 -72
- package/dist/init-template/app/ui/src/assets/app-icon.svg +0 -1
- package/dist/init-template/app/ui/src/components/setup/steps/complete/index.tsx +0 -32
- package/dist/init-template/app/ui/src/components/setup/steps/first/index.tsx +0 -27
- package/dist/init-template/app/ui/src/components/setup/steps/index.tsx +0 -22
- package/dist/init-template/app/ui/src/components/setup/steps/second/index.tsx +0 -38
- package/dist/init-template/app/ui/src/index.tsx +0 -45
- package/dist/init-template/app/ui/src/routes/home/index.tsx +0 -21
- package/dist/init-template/app/ui/src/vite-env.d.ts +0 -13
- package/dist/init-template/app/ui/tsconfig.json +0 -35
- package/dist/init-template/app/ui/tsconfig.node.json +0 -10
- package/dist/init-template/app/ui/vite.config.mts +0 -48
- package/dist/init-template/package.json +0 -46
- package/dist/init-template/tsconfig.json +0 -24
|
@@ -1,592 +0,0 @@
|
|
|
1
|
-
# Testing Seeka Apps
|
|
2
|
-
|
|
3
|
-
This guide covers testing strategies and best practices for Seeka apps.
|
|
4
|
-
|
|
5
|
-
## Testing Stack
|
|
6
|
-
|
|
7
|
-
Seeka apps use the following testing tools:
|
|
8
|
-
|
|
9
|
-
| Tool | Purpose |
|
|
10
|
-
|------|---------|
|
|
11
|
-
| Jest | Test runner and assertion library |
|
|
12
|
-
| ts-jest | TypeScript support for Jest |
|
|
13
|
-
|
|
14
|
-
## Test Structure
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
app/
|
|
18
|
-
├── server/
|
|
19
|
-
│ └── src/
|
|
20
|
-
│ └── app/
|
|
21
|
-
│ └── services/
|
|
22
|
-
│ ├── myService.ts
|
|
23
|
-
│ └── __tests__/
|
|
24
|
-
│ └── myService.test.ts
|
|
25
|
-
└── lib/
|
|
26
|
-
└── src/
|
|
27
|
-
├── validation/
|
|
28
|
-
│ ├── index.ts
|
|
29
|
-
│ └── __tests__/
|
|
30
|
-
│ └── index.test.ts
|
|
31
|
-
└── models/
|
|
32
|
-
└── __tests__/
|
|
33
|
-
└── index.test.ts
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Unit Testing
|
|
37
|
-
|
|
38
|
-
### Testing Services
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
// services/__tests__/externalPlatform.test.ts
|
|
42
|
-
import { ExternalPlatformService } from '../externalPlatform';
|
|
43
|
-
|
|
44
|
-
// Mock logger
|
|
45
|
-
const mockLogger = {
|
|
46
|
-
debug: jest.fn(),
|
|
47
|
-
info: jest.fn(),
|
|
48
|
-
warn: jest.fn(),
|
|
49
|
-
error: jest.fn()
|
|
50
|
-
} as any;
|
|
51
|
-
|
|
52
|
-
describe('ExternalPlatformService', () => {
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
jest.clearAllMocks();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('sendEvent', () => {
|
|
58
|
-
it('should send event successfully', async () => {
|
|
59
|
-
// Arrange
|
|
60
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
61
|
-
ok: true,
|
|
62
|
-
json: () => Promise.resolve({ success: true })
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const service = new ExternalPlatformService({
|
|
66
|
-
apiKey: 'test-api-key',
|
|
67
|
-
accountId: 'test-account'
|
|
68
|
-
}, mockLogger);
|
|
69
|
-
|
|
70
|
-
// Act
|
|
71
|
-
const result = await service.sendEvent({
|
|
72
|
-
event_name: 'Purchase',
|
|
73
|
-
value: 100
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Assert
|
|
77
|
-
expect(result).toBe(true);
|
|
78
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
79
|
-
expect.stringContaining('/events'),
|
|
80
|
-
expect.objectContaining({
|
|
81
|
-
method: 'POST',
|
|
82
|
-
headers: expect.objectContaining({
|
|
83
|
-
'Authorization': 'Bearer test-api-key'
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should handle API errors', async () => {
|
|
90
|
-
// Arrange
|
|
91
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
92
|
-
ok: false,
|
|
93
|
-
status: 400,
|
|
94
|
-
text: () => Promise.resolve('Bad request')
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const service = new ExternalPlatformService({
|
|
98
|
-
apiKey: 'test-api-key',
|
|
99
|
-
accountId: 'test-account'
|
|
100
|
-
}, mockLogger);
|
|
101
|
-
|
|
102
|
-
// Act & Assert
|
|
103
|
-
await expect(service.sendEvent({ event_name: 'Test' }))
|
|
104
|
-
.rejects.toThrow('API error');
|
|
105
|
-
|
|
106
|
-
expect(mockLogger.error).toHaveBeenCalled();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should handle network errors', async () => {
|
|
110
|
-
// Arrange
|
|
111
|
-
global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
|
|
112
|
-
|
|
113
|
-
const service = new ExternalPlatformService({
|
|
114
|
-
apiKey: 'test-api-key',
|
|
115
|
-
accountId: 'test-account'
|
|
116
|
-
}, mockLogger);
|
|
117
|
-
|
|
118
|
-
// Act & Assert
|
|
119
|
-
await expect(service.sendEvent({ event_name: 'Test' }))
|
|
120
|
-
.rejects.toThrow('Network error');
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Testing Validation
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
// validation/__tests__/index.test.ts
|
|
130
|
-
import { validateInstallationSettings } from '../index';
|
|
131
|
-
|
|
132
|
-
const mockLogger = {
|
|
133
|
-
debug: jest.fn(),
|
|
134
|
-
info: jest.fn(),
|
|
135
|
-
warn: jest.fn(),
|
|
136
|
-
error: jest.fn()
|
|
137
|
-
} as any;
|
|
138
|
-
|
|
139
|
-
describe('validateInstallationSettings', () => {
|
|
140
|
-
beforeEach(() => {
|
|
141
|
-
jest.clearAllMocks();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should return null for valid settings', async () => {
|
|
145
|
-
const settings = {
|
|
146
|
-
apiKey: 'valid-api-key',
|
|
147
|
-
accountId: 'valid-account-id'
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const result = await validateInstallationSettings(settings, mockLogger);
|
|
151
|
-
|
|
152
|
-
expect(result).toBeNull();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should return error for missing apiKey', async () => {
|
|
156
|
-
const settings = {
|
|
157
|
-
accountId: 'valid-account-id'
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const result = await validateInstallationSettings(settings as any, mockLogger);
|
|
161
|
-
|
|
162
|
-
expect(result).toBe('API Key is required');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should return error for missing accountId', async () => {
|
|
166
|
-
const settings = {
|
|
167
|
-
apiKey: 'valid-api-key'
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const result = await validateInstallationSettings(settings as any, mockLogger);
|
|
171
|
-
|
|
172
|
-
expect(result).toBe('Account ID is required');
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Testing Data Transformations
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
// services/__tests__/dataMapper.test.ts
|
|
181
|
-
import { mapActivityToPlatformEvent } from '../dataMapper';
|
|
182
|
-
|
|
183
|
-
describe('mapActivityToPlatformEvent', () => {
|
|
184
|
-
it('should map activity to platform event format', () => {
|
|
185
|
-
const activity = {
|
|
186
|
-
activity: {
|
|
187
|
-
activityId: 'act-123',
|
|
188
|
-
activityName: 'Purchase',
|
|
189
|
-
timestamp: '2024-01-15T10:30:00Z',
|
|
190
|
-
properties: {
|
|
191
|
-
currency: 'USD',
|
|
192
|
-
value: 99.99
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
person: {
|
|
196
|
-
personId: 'person-123',
|
|
197
|
-
emailHashed: 'abc123hash',
|
|
198
|
-
phoneHashed: 'def456hash'
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const result = mapActivityToPlatformEvent(activity);
|
|
203
|
-
|
|
204
|
-
expect(result).toEqual({
|
|
205
|
-
event_name: 'Purchase',
|
|
206
|
-
event_time: expect.any(Number),
|
|
207
|
-
user_data: {
|
|
208
|
-
em: ['abc123hash'],
|
|
209
|
-
ph: ['def456hash']
|
|
210
|
-
},
|
|
211
|
-
custom_data: {
|
|
212
|
-
currency: 'USD',
|
|
213
|
-
value: 99.99,
|
|
214
|
-
content_ids: undefined
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('should handle missing person data', () => {
|
|
220
|
-
const activity = {
|
|
221
|
-
activity: {
|
|
222
|
-
activityId: 'act-123',
|
|
223
|
-
activityName: 'PageView',
|
|
224
|
-
timestamp: '2024-01-15T10:30:00Z'
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
const result = mapActivityToPlatformEvent(activity);
|
|
229
|
-
|
|
230
|
-
expect(result.user_data.em).toBeUndefined();
|
|
231
|
-
expect(result.user_data.ph).toBeUndefined();
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('should map activity names correctly', () => {
|
|
235
|
-
const testCases = [
|
|
236
|
-
{ input: 'PageView', expected: 'ViewContent' },
|
|
237
|
-
{ input: 'Purchase', expected: 'Purchase' },
|
|
238
|
-
{ input: 'AddToCart', expected: 'AddToCart' },
|
|
239
|
-
{ input: 'CustomEvent', expected: 'CustomEvent' }
|
|
240
|
-
];
|
|
241
|
-
|
|
242
|
-
testCases.forEach(({ input, expected }) => {
|
|
243
|
-
const activity = {
|
|
244
|
-
activity: {
|
|
245
|
-
activityId: 'act-123',
|
|
246
|
-
activityName: input,
|
|
247
|
-
timestamp: '2024-01-15T10:30:00Z'
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const result = mapActivityToPlatformEvent(activity);
|
|
252
|
-
expect(result.event_name).toBe(expected);
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
## Integration Testing
|
|
259
|
-
|
|
260
|
-
### Testing Webhook Handlers
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
// functions/__tests__/seekaAppWebhook.test.ts
|
|
264
|
-
import { seekaAppWebhook } from '../seekaAppWebhook';
|
|
265
|
-
import { InvocationContext } from '@azure/functions';
|
|
266
|
-
|
|
267
|
-
// Mock SDK functions
|
|
268
|
-
jest.mock('@seeka-labs/sdk-apps-server', () => ({
|
|
269
|
-
throwOnInvalidWebhookSignature: jest.fn(),
|
|
270
|
-
SeekaWebhookCallType: {
|
|
271
|
-
Probe: 'Probe',
|
|
272
|
-
AppInstalled: 'AppInstalled',
|
|
273
|
-
ActivityAccepted: 'ActivityAccepted'
|
|
274
|
-
}
|
|
275
|
-
}));
|
|
276
|
-
|
|
277
|
-
jest.mock('@seeka-labs/sdk-apps-server-host', () => ({
|
|
278
|
-
tryGetInstallation: jest.fn(),
|
|
279
|
-
createOrUpdateInstallation: jest.fn(),
|
|
280
|
-
startServices: jest.fn(),
|
|
281
|
-
webhookLogger: jest.fn(() => mockLogger),
|
|
282
|
-
childLogger: jest.fn(() => mockLogger)
|
|
283
|
-
}));
|
|
284
|
-
|
|
285
|
-
const mockLogger = {
|
|
286
|
-
debug: jest.fn(),
|
|
287
|
-
info: jest.fn(),
|
|
288
|
-
verbose: jest.fn(),
|
|
289
|
-
warn: jest.fn(),
|
|
290
|
-
error: jest.fn(),
|
|
291
|
-
profile: jest.fn()
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
describe('seekaAppWebhook', () => {
|
|
295
|
-
const mockContext = {
|
|
296
|
-
invocationId: 'test-invocation-id'
|
|
297
|
-
} as InvocationContext;
|
|
298
|
-
|
|
299
|
-
beforeEach(() => {
|
|
300
|
-
jest.clearAllMocks();
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('should return 204 for probe requests', async () => {
|
|
304
|
-
const req = createMockRequest({
|
|
305
|
-
type: 'Probe'
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
const result = await seekaAppWebhook(req, mockContext);
|
|
309
|
-
|
|
310
|
-
expect(result.status).toBe(204);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('should return 400 for missing body', async () => {
|
|
314
|
-
const req = createMockRequest(null);
|
|
315
|
-
|
|
316
|
-
const result = await seekaAppWebhook(req, mockContext);
|
|
317
|
-
|
|
318
|
-
expect(result.status).toBe(400);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('should return 401 for invalid signature', async () => {
|
|
322
|
-
const { throwOnInvalidWebhookSignature } = require('@seeka-labs/sdk-apps-server');
|
|
323
|
-
throwOnInvalidWebhookSignature.mockImplementation(() => {
|
|
324
|
-
throw new Error('Invalid signature');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
const req = createMockRequest({
|
|
328
|
-
type: 'AppInstalled',
|
|
329
|
-
context: { applicationInstallId: 'test-id' }
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
const result = await seekaAppWebhook(req, mockContext);
|
|
333
|
-
|
|
334
|
-
expect(result.status).toBe(401);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
function createMockRequest(body: any) {
|
|
339
|
-
return {
|
|
340
|
-
text: () => Promise.resolve(body ? JSON.stringify(body) : ''),
|
|
341
|
-
headers: new Map([
|
|
342
|
-
['x-seeka-signature', 'test-signature']
|
|
343
|
-
])
|
|
344
|
-
} as any;
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Testing Queue Handlers
|
|
349
|
-
|
|
350
|
-
```typescript
|
|
351
|
-
// functions/__tests__/trackActivityQueueHandler.test.ts
|
|
352
|
-
import { processQueueItem } from '../trackActivityQueueHandler';
|
|
353
|
-
|
|
354
|
-
jest.mock('@seeka-labs/sdk-apps-server-host', () => ({
|
|
355
|
-
tryGetInstallation: jest.fn(),
|
|
356
|
-
startServices: jest.fn(),
|
|
357
|
-
childLogger: jest.fn(() => mockLogger)
|
|
358
|
-
}));
|
|
359
|
-
|
|
360
|
-
jest.mock('../../app/services/activities', () => ({
|
|
361
|
-
processActivity: jest.fn()
|
|
362
|
-
}));
|
|
363
|
-
|
|
364
|
-
const mockLogger = {
|
|
365
|
-
debug: jest.fn(),
|
|
366
|
-
info: jest.fn(),
|
|
367
|
-
error: jest.fn()
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
describe('trackActivityQueueHandler', () => {
|
|
371
|
-
beforeEach(() => {
|
|
372
|
-
jest.clearAllMocks();
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
it('should process activity successfully', async () => {
|
|
376
|
-
const { tryGetInstallation } = require('@seeka-labs/sdk-apps-server-host');
|
|
377
|
-
const { processActivity } = require('../../app/services/activities');
|
|
378
|
-
|
|
379
|
-
tryGetInstallation.mockResolvedValue({
|
|
380
|
-
applicationInstallId: 'test-install-id',
|
|
381
|
-
installationState: {
|
|
382
|
-
installationSettings: { apiKey: 'test-key' }
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
const queueItem = {
|
|
387
|
-
payload: { activity: { activityId: 'act-123' } },
|
|
388
|
-
applicationInstallId: 'test-install-id'
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
await processQueueItem(queueItem, mockLogger);
|
|
392
|
-
|
|
393
|
-
expect(processActivity).toHaveBeenCalled();
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('should handle missing installation', async () => {
|
|
397
|
-
const { tryGetInstallation } = require('@seeka-labs/sdk-apps-server-host');
|
|
398
|
-
tryGetInstallation.mockResolvedValue(null);
|
|
399
|
-
|
|
400
|
-
const queueItem = {
|
|
401
|
-
payload: { activity: { activityId: 'act-123' } },
|
|
402
|
-
applicationInstallId: 'unknown-id'
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
await processQueueItem(queueItem, mockLogger);
|
|
406
|
-
|
|
407
|
-
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
408
|
-
'Installation not found',
|
|
409
|
-
expect.any(Object)
|
|
410
|
-
);
|
|
411
|
-
});
|
|
412
|
-
});
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
## Mocking Patterns
|
|
416
|
-
|
|
417
|
-
### Mocking External APIs
|
|
418
|
-
|
|
419
|
-
```typescript
|
|
420
|
-
// __mocks__/externalApi.ts
|
|
421
|
-
export const mockExternalApi = {
|
|
422
|
-
sendEvent: jest.fn().mockResolvedValue({ success: true }),
|
|
423
|
-
getAccount: jest.fn().mockResolvedValue({ id: 'account-123' })
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
// In test file
|
|
427
|
-
jest.mock('../services/externalApi', () => mockExternalApi);
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### Mocking Fetch
|
|
431
|
-
|
|
432
|
-
```typescript
|
|
433
|
-
// Global fetch mock
|
|
434
|
-
beforeEach(() => {
|
|
435
|
-
global.fetch = jest.fn();
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
afterEach(() => {
|
|
439
|
-
jest.restoreAllMocks();
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
// Success response
|
|
443
|
-
(fetch as jest.Mock).mockResolvedValue({
|
|
444
|
-
ok: true,
|
|
445
|
-
status: 200,
|
|
446
|
-
json: () => Promise.resolve({ data: 'test' })
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
// Error response
|
|
450
|
-
(fetch as jest.Mock).mockResolvedValue({
|
|
451
|
-
ok: false,
|
|
452
|
-
status: 500,
|
|
453
|
-
text: () => Promise.resolve('Server error')
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
// Network error
|
|
457
|
-
(fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
### Mocking SDK Functions
|
|
461
|
-
|
|
462
|
-
```typescript
|
|
463
|
-
jest.mock('@seeka-labs/sdk-apps-server-host', () => ({
|
|
464
|
-
getInstallation: jest.fn().mockResolvedValue({
|
|
465
|
-
applicationInstallId: 'test-id',
|
|
466
|
-
installationState: {
|
|
467
|
-
installationSettings: {
|
|
468
|
-
apiKey: 'test-key'
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}),
|
|
472
|
-
createOrUpdateInstallation: jest.fn().mockResolvedValue(undefined),
|
|
473
|
-
triggerBackgroundJob: jest.fn().mockResolvedValue(undefined)
|
|
474
|
-
}));
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
## Running Tests
|
|
478
|
-
|
|
479
|
-
### Commands
|
|
480
|
-
|
|
481
|
-
```bash
|
|
482
|
-
# Run all tests
|
|
483
|
-
yarn test
|
|
484
|
-
|
|
485
|
-
# Run tests in watch mode
|
|
486
|
-
yarn test --watch
|
|
487
|
-
|
|
488
|
-
# Run specific test file
|
|
489
|
-
yarn test myService.test.ts
|
|
490
|
-
|
|
491
|
-
# Run with coverage
|
|
492
|
-
yarn test --coverage
|
|
493
|
-
|
|
494
|
-
# Run tests matching pattern
|
|
495
|
-
yarn test --testNamePattern="should send event"
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
### Jest Configuration
|
|
499
|
-
|
|
500
|
-
```javascript
|
|
501
|
-
// jest.config.js
|
|
502
|
-
module.exports = {
|
|
503
|
-
preset: 'ts-jest',
|
|
504
|
-
testEnvironment: 'node',
|
|
505
|
-
roots: ['<rootDir>/src'],
|
|
506
|
-
testMatch: ['**/__tests__/**/*.test.ts'],
|
|
507
|
-
collectCoverageFrom: [
|
|
508
|
-
'src/**/*.ts',
|
|
509
|
-
'!src/**/*.d.ts',
|
|
510
|
-
'!src/**/__tests__/**'
|
|
511
|
-
],
|
|
512
|
-
coverageThreshold: {
|
|
513
|
-
global: {
|
|
514
|
-
branches: 80,
|
|
515
|
-
functions: 80,
|
|
516
|
-
lines: 80,
|
|
517
|
-
statements: 80
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
## Best Practices
|
|
524
|
-
|
|
525
|
-
### 1. Test Organization
|
|
526
|
-
|
|
527
|
-
- Place tests in `__tests__` directories next to source files
|
|
528
|
-
- Name test files with `.test.ts` suffix
|
|
529
|
-
- Group related tests with `describe` blocks
|
|
530
|
-
- Use clear, descriptive test names
|
|
531
|
-
|
|
532
|
-
### 2. Test Independence
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
// Each test should be independent
|
|
536
|
-
beforeEach(() => {
|
|
537
|
-
jest.clearAllMocks();
|
|
538
|
-
// Reset any shared state
|
|
539
|
-
});
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
### 3. Arrange-Act-Assert Pattern
|
|
543
|
-
|
|
544
|
-
```typescript
|
|
545
|
-
it('should do something', async () => {
|
|
546
|
-
// Arrange - set up test data and mocks
|
|
547
|
-
const input = { data: 'test' };
|
|
548
|
-
mockService.process.mockResolvedValue({ success: true });
|
|
549
|
-
|
|
550
|
-
// Act - execute the code under test
|
|
551
|
-
const result = await myFunction(input);
|
|
552
|
-
|
|
553
|
-
// Assert - verify the results
|
|
554
|
-
expect(result.success).toBe(true);
|
|
555
|
-
expect(mockService.process).toHaveBeenCalledWith(input);
|
|
556
|
-
});
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
### 4. Test Edge Cases
|
|
560
|
-
|
|
561
|
-
```typescript
|
|
562
|
-
describe('edge cases', () => {
|
|
563
|
-
it('should handle empty input', async () => { /* ... */ });
|
|
564
|
-
it('should handle null values', async () => { /* ... */ });
|
|
565
|
-
it('should handle very large inputs', async () => { /* ... */ });
|
|
566
|
-
it('should handle special characters', async () => { /* ... */ });
|
|
567
|
-
});
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
### 5. Error Testing
|
|
571
|
-
|
|
572
|
-
```typescript
|
|
573
|
-
it('should throw on invalid input', async () => {
|
|
574
|
-
await expect(myFunction(null))
|
|
575
|
-
.rejects.toThrow('Input is required');
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
it('should handle API errors gracefully', async () => {
|
|
579
|
-
mockApi.call.mockRejectedValue(new Error('API error'));
|
|
580
|
-
|
|
581
|
-
const result = await myFunction(input);
|
|
582
|
-
|
|
583
|
-
expect(result.success).toBe(false);
|
|
584
|
-
expect(result.error).toBe('API error');
|
|
585
|
-
});
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
## See Also
|
|
589
|
-
|
|
590
|
-
- `getting-started.md` - Initial setup guide
|
|
591
|
-
- `common-patterns.md` - Implementation patterns
|
|
592
|
-
- Jest documentation: https://jestjs.io/docs/getting-started
|