@aj-archipelago/cortex 1.3.55 → 1.3.57
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/.env.sample +3 -1
- package/config/default.example.json +2 -2
- package/config.js +32 -0
- package/helper-apps/mogrt-handler/.env.example +24 -0
- package/helper-apps/mogrt-handler/README.md +166 -0
- package/helper-apps/mogrt-handler/glossaryHandler.js +218 -0
- package/helper-apps/mogrt-handler/index.js +213 -0
- package/helper-apps/mogrt-handler/package-lock.json +7106 -0
- package/helper-apps/mogrt-handler/package.json +34 -0
- package/helper-apps/mogrt-handler/s3Handler.js +444 -0
- package/helper-apps/mogrt-handler/start.js +98 -0
- package/helper-apps/mogrt-handler/swagger.js +42 -0
- package/helper-apps/mogrt-handler/swagger.yaml +436 -0
- package/helper-apps/mogrt-handler/tests/integration/api.test.js +226 -0
- package/helper-apps/mogrt-handler/tests/integration/glossary.test.js +106 -0
- package/helper-apps/mogrt-handler/tests/setup.js +8 -0
- package/helper-apps/mogrt-handler/tests/test-files/test.gif +1 -0
- package/helper-apps/mogrt-handler/tests/test-files/test.mogrt +1 -0
- package/helper-apps/mogrt-handler/tests/test-files/test.mp4 +1 -0
- package/helper-apps/mogrt-handler/tests/unit/glossary.unit.test.js +118 -0
- package/helper-apps/mogrt-handler/tests/unit/index.test.js +349 -0
- package/helper-apps/mogrt-handler/tests/unit/s3Handler.test.js +204 -0
- package/helper-apps/mogrt-handler/tests/unit/sample.test.js +28 -0
- package/helper-apps/mogrt-handler/vitest.config.js +15 -0
- package/lib/entityConstants.js +1 -1
- package/lib/requestExecutor.js +1 -1
- package/package.json +1 -1
- package/pathways/list_translation_models.js +67 -0
- package/pathways/system/sys_test_response_reasonableness.js +20 -0
- package/pathways/system/workspaces/workspace_applet_edit.js +187 -0
- package/pathways/translate_apptek.js +11 -0
- package/pathways/translate_google.js +10 -0
- package/pathways/translate_groq.js +36 -0
- package/pathways/video_seedance.js +17 -0
- package/pathways/video_veo.js +31 -0
- package/server/modelExecutor.js +16 -0
- package/server/plugins/apptekTranslatePlugin.js +189 -0
- package/server/plugins/googleTranslatePlugin.js +121 -0
- package/server/plugins/groqChatPlugin.js +108 -0
- package/server/plugins/replicateApiPlugin.js +22 -0
- package/server/plugins/veoVideoPlugin.js +218 -0
- package/tests/apptekTranslatePlugin.test.js +228 -0
- package/tests/integration/apptekTranslatePlugin.integration.test.js +156 -0
- package/tests/translate_apptek.test.js +117 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
openapi: 3.0.0
|
|
2
|
+
info:
|
|
3
|
+
title: MOGRT Handler API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: API for handling MOGRT files and preview GIFs with S3 storage
|
|
6
|
+
servers:
|
|
7
|
+
- url: http://localhost:7072
|
|
8
|
+
description: Development server
|
|
9
|
+
|
|
10
|
+
tags:
|
|
11
|
+
- name: MOGRT Management
|
|
12
|
+
description: Endpoints for managing MOGRT files and manifests
|
|
13
|
+
- name: Glossary Management
|
|
14
|
+
description: Endpoints for managing translation glossaries
|
|
15
|
+
- name: Glossary Versioning
|
|
16
|
+
description: Endpoints for working with glossary versions
|
|
17
|
+
|
|
18
|
+
paths:
|
|
19
|
+
/api/MogrtHandler:
|
|
20
|
+
get:
|
|
21
|
+
tags:
|
|
22
|
+
- MOGRT Management
|
|
23
|
+
summary: Get MOGRT manifest
|
|
24
|
+
parameters:
|
|
25
|
+
- in: query
|
|
26
|
+
name: manifestId
|
|
27
|
+
schema:
|
|
28
|
+
type: string
|
|
29
|
+
description: ID of the manifest to retrieve. If not provided, returns master manifest.
|
|
30
|
+
responses:
|
|
31
|
+
'200':
|
|
32
|
+
description: Returns the requested manifest
|
|
33
|
+
'500':
|
|
34
|
+
description: Server error
|
|
35
|
+
post:
|
|
36
|
+
tags:
|
|
37
|
+
- MOGRT Management
|
|
38
|
+
summary: Upload MOGRT file
|
|
39
|
+
requestBody:
|
|
40
|
+
required: true
|
|
41
|
+
content:
|
|
42
|
+
multipart/form-data:
|
|
43
|
+
schema:
|
|
44
|
+
type: object
|
|
45
|
+
properties:
|
|
46
|
+
file:
|
|
47
|
+
type: string
|
|
48
|
+
format: binary
|
|
49
|
+
description: MOGRT file to upload
|
|
50
|
+
preview:
|
|
51
|
+
type: string
|
|
52
|
+
format: binary
|
|
53
|
+
description: Preview GIF/PNG file
|
|
54
|
+
name:
|
|
55
|
+
type: string
|
|
56
|
+
description: Display name for the MOGRT
|
|
57
|
+
id:
|
|
58
|
+
type: string
|
|
59
|
+
description: Optional ID to use (will be generated if not provided)
|
|
60
|
+
manifestId:
|
|
61
|
+
type: string
|
|
62
|
+
description: Optional manifest ID to add MOGRT to
|
|
63
|
+
responses:
|
|
64
|
+
'200':
|
|
65
|
+
description: MOGRT uploaded successfully
|
|
66
|
+
'400':
|
|
67
|
+
description: Bad request
|
|
68
|
+
'500':
|
|
69
|
+
description: Server error
|
|
70
|
+
|
|
71
|
+
/api/MogrtHandler/{id}:
|
|
72
|
+
delete:
|
|
73
|
+
tags:
|
|
74
|
+
- MOGRT Management
|
|
75
|
+
summary: Delete MOGRT from manifest
|
|
76
|
+
parameters:
|
|
77
|
+
- in: path
|
|
78
|
+
name: id
|
|
79
|
+
schema:
|
|
80
|
+
type: string
|
|
81
|
+
required: true
|
|
82
|
+
description: ID of the MOGRT to delete
|
|
83
|
+
- in: query
|
|
84
|
+
name: manifestId
|
|
85
|
+
schema:
|
|
86
|
+
type: string
|
|
87
|
+
description: Optional manifest ID to delete from (defaults to master)
|
|
88
|
+
responses:
|
|
89
|
+
'200':
|
|
90
|
+
description: MOGRT deleted
|
|
91
|
+
'404':
|
|
92
|
+
description: MOGRT not found
|
|
93
|
+
'500':
|
|
94
|
+
description: Server error
|
|
95
|
+
|
|
96
|
+
/api/glossary/list:
|
|
97
|
+
get:
|
|
98
|
+
tags:
|
|
99
|
+
- Glossary Management
|
|
100
|
+
summary: List all glossaries
|
|
101
|
+
responses:
|
|
102
|
+
'200':
|
|
103
|
+
description: Returns list of glossaries
|
|
104
|
+
content:
|
|
105
|
+
application/json:
|
|
106
|
+
schema:
|
|
107
|
+
type: object
|
|
108
|
+
properties:
|
|
109
|
+
glossaries:
|
|
110
|
+
type: array
|
|
111
|
+
items:
|
|
112
|
+
type: object
|
|
113
|
+
properties:
|
|
114
|
+
id:
|
|
115
|
+
type: string
|
|
116
|
+
name:
|
|
117
|
+
type: string
|
|
118
|
+
source_lang_code:
|
|
119
|
+
type: string
|
|
120
|
+
target_lang_code:
|
|
121
|
+
type: string
|
|
122
|
+
'500':
|
|
123
|
+
description: Server error
|
|
124
|
+
|
|
125
|
+
/api/glossary/{langPair}:
|
|
126
|
+
post:
|
|
127
|
+
tags:
|
|
128
|
+
- Glossary Management
|
|
129
|
+
summary: Create a new glossary
|
|
130
|
+
parameters:
|
|
131
|
+
- in: path
|
|
132
|
+
name: langPair
|
|
133
|
+
schema:
|
|
134
|
+
type: string
|
|
135
|
+
required: true
|
|
136
|
+
description: The language pair in format 'xx-xx' (e.g., 'en-es')
|
|
137
|
+
- in: query
|
|
138
|
+
name: name
|
|
139
|
+
schema:
|
|
140
|
+
type: string
|
|
141
|
+
description: Name of the glossary
|
|
142
|
+
requestBody:
|
|
143
|
+
required: true
|
|
144
|
+
content:
|
|
145
|
+
application/json:
|
|
146
|
+
schema:
|
|
147
|
+
type: object
|
|
148
|
+
required:
|
|
149
|
+
- source_lang_code
|
|
150
|
+
- target_lang_code
|
|
151
|
+
- entries
|
|
152
|
+
properties:
|
|
153
|
+
source_lang_code:
|
|
154
|
+
type: string
|
|
155
|
+
example: en
|
|
156
|
+
target_lang_code:
|
|
157
|
+
type: string
|
|
158
|
+
example: es
|
|
159
|
+
name:
|
|
160
|
+
type: string
|
|
161
|
+
example: My Glossary
|
|
162
|
+
entries:
|
|
163
|
+
type: array
|
|
164
|
+
items:
|
|
165
|
+
type: object
|
|
166
|
+
required:
|
|
167
|
+
- source_text
|
|
168
|
+
- target_text
|
|
169
|
+
properties:
|
|
170
|
+
source_text:
|
|
171
|
+
type: string
|
|
172
|
+
example: hello
|
|
173
|
+
target_text:
|
|
174
|
+
type: string
|
|
175
|
+
example: hola
|
|
176
|
+
responses:
|
|
177
|
+
'200':
|
|
178
|
+
description: Glossary created
|
|
179
|
+
content:
|
|
180
|
+
application/json:
|
|
181
|
+
schema:
|
|
182
|
+
type: object
|
|
183
|
+
properties:
|
|
184
|
+
glossary_id:
|
|
185
|
+
type: string
|
|
186
|
+
version:
|
|
187
|
+
type: object
|
|
188
|
+
properties:
|
|
189
|
+
versionId:
|
|
190
|
+
type: string
|
|
191
|
+
key:
|
|
192
|
+
type: string
|
|
193
|
+
'400':
|
|
194
|
+
description: Bad request
|
|
195
|
+
'500':
|
|
196
|
+
description: Server error
|
|
197
|
+
|
|
198
|
+
/api/glossary/{id}:
|
|
199
|
+
get:
|
|
200
|
+
tags:
|
|
201
|
+
- Glossary Management
|
|
202
|
+
summary: Get a glossary by ID
|
|
203
|
+
parameters:
|
|
204
|
+
- in: path
|
|
205
|
+
name: id
|
|
206
|
+
schema:
|
|
207
|
+
type: string
|
|
208
|
+
required: true
|
|
209
|
+
description: The glossary ID
|
|
210
|
+
responses:
|
|
211
|
+
'200':
|
|
212
|
+
description: Glossary details
|
|
213
|
+
content:
|
|
214
|
+
application/json:
|
|
215
|
+
schema:
|
|
216
|
+
type: object
|
|
217
|
+
properties:
|
|
218
|
+
glossary_id:
|
|
219
|
+
type: string
|
|
220
|
+
name:
|
|
221
|
+
type: string
|
|
222
|
+
source_lang_code:
|
|
223
|
+
type: string
|
|
224
|
+
target_lang_code:
|
|
225
|
+
type: string
|
|
226
|
+
entries:
|
|
227
|
+
type: array
|
|
228
|
+
items:
|
|
229
|
+
type: object
|
|
230
|
+
properties:
|
|
231
|
+
source_text:
|
|
232
|
+
type: string
|
|
233
|
+
target_text:
|
|
234
|
+
type: string
|
|
235
|
+
'404':
|
|
236
|
+
description: Glossary not found
|
|
237
|
+
'500':
|
|
238
|
+
description: Server error
|
|
239
|
+
delete:
|
|
240
|
+
tags:
|
|
241
|
+
- Glossary Management
|
|
242
|
+
summary: Delete a glossary by ID
|
|
243
|
+
parameters:
|
|
244
|
+
- in: path
|
|
245
|
+
name: id
|
|
246
|
+
schema:
|
|
247
|
+
type: string
|
|
248
|
+
required: true
|
|
249
|
+
description: The glossary ID
|
|
250
|
+
responses:
|
|
251
|
+
'200':
|
|
252
|
+
description: Glossary deleted
|
|
253
|
+
'404':
|
|
254
|
+
description: Glossary not found
|
|
255
|
+
'500':
|
|
256
|
+
description: Server error
|
|
257
|
+
|
|
258
|
+
/api/glossary/edit/{id}:
|
|
259
|
+
post:
|
|
260
|
+
tags:
|
|
261
|
+
- Glossary Management
|
|
262
|
+
summary: Edit a glossary by ID (delete and recreate)
|
|
263
|
+
parameters:
|
|
264
|
+
- in: path
|
|
265
|
+
name: id
|
|
266
|
+
schema:
|
|
267
|
+
type: string
|
|
268
|
+
required: true
|
|
269
|
+
description: The glossary ID
|
|
270
|
+
requestBody:
|
|
271
|
+
required: true
|
|
272
|
+
content:
|
|
273
|
+
application/json:
|
|
274
|
+
schema:
|
|
275
|
+
type: object
|
|
276
|
+
required:
|
|
277
|
+
- source_lang_code
|
|
278
|
+
- target_lang_code
|
|
279
|
+
- entries
|
|
280
|
+
properties:
|
|
281
|
+
source_lang_code:
|
|
282
|
+
type: string
|
|
283
|
+
example: en
|
|
284
|
+
target_lang_code:
|
|
285
|
+
type: string
|
|
286
|
+
example: es
|
|
287
|
+
name:
|
|
288
|
+
type: string
|
|
289
|
+
example: My Glossary
|
|
290
|
+
entries:
|
|
291
|
+
type: array
|
|
292
|
+
items:
|
|
293
|
+
type: object
|
|
294
|
+
required:
|
|
295
|
+
- source_text
|
|
296
|
+
- target_text
|
|
297
|
+
properties:
|
|
298
|
+
source_text:
|
|
299
|
+
type: string
|
|
300
|
+
example: hello
|
|
301
|
+
target_text:
|
|
302
|
+
type: string
|
|
303
|
+
example: hola
|
|
304
|
+
responses:
|
|
305
|
+
'200':
|
|
306
|
+
description: Glossary edited
|
|
307
|
+
content:
|
|
308
|
+
application/json:
|
|
309
|
+
schema:
|
|
310
|
+
type: object
|
|
311
|
+
properties:
|
|
312
|
+
glossary_id:
|
|
313
|
+
type: string
|
|
314
|
+
version:
|
|
315
|
+
type: object
|
|
316
|
+
properties:
|
|
317
|
+
versionId:
|
|
318
|
+
type: string
|
|
319
|
+
key:
|
|
320
|
+
type: string
|
|
321
|
+
'400':
|
|
322
|
+
description: Bad request
|
|
323
|
+
'404':
|
|
324
|
+
description: Glossary not found
|
|
325
|
+
'500':
|
|
326
|
+
description: Server error
|
|
327
|
+
|
|
328
|
+
/api/glossary/{langPair}/versions/{glossaryId}:
|
|
329
|
+
get:
|
|
330
|
+
tags:
|
|
331
|
+
- Glossary Versioning
|
|
332
|
+
summary: Get all versions of a glossary
|
|
333
|
+
parameters:
|
|
334
|
+
- in: path
|
|
335
|
+
name: langPair
|
|
336
|
+
schema:
|
|
337
|
+
type: string
|
|
338
|
+
required: true
|
|
339
|
+
description: The language pair in format 'xx-xx' (e.g., 'en-es')
|
|
340
|
+
- in: path
|
|
341
|
+
name: glossaryId
|
|
342
|
+
schema:
|
|
343
|
+
type: string
|
|
344
|
+
required: true
|
|
345
|
+
description: The glossary ID
|
|
346
|
+
- in: query
|
|
347
|
+
name: name
|
|
348
|
+
schema:
|
|
349
|
+
type: string
|
|
350
|
+
description: Optional name of the glossary
|
|
351
|
+
responses:
|
|
352
|
+
'200':
|
|
353
|
+
description: List of glossary versions
|
|
354
|
+
content:
|
|
355
|
+
application/json:
|
|
356
|
+
schema:
|
|
357
|
+
type: object
|
|
358
|
+
properties:
|
|
359
|
+
versions:
|
|
360
|
+
type: array
|
|
361
|
+
items:
|
|
362
|
+
type: object
|
|
363
|
+
properties:
|
|
364
|
+
versionId:
|
|
365
|
+
type: string
|
|
366
|
+
description: S3 version ID
|
|
367
|
+
glossaryId:
|
|
368
|
+
type: string
|
|
369
|
+
description: Glossary ID
|
|
370
|
+
lastModified:
|
|
371
|
+
type: string
|
|
372
|
+
format: date-time
|
|
373
|
+
description: Version creation timestamp
|
|
374
|
+
isLatest:
|
|
375
|
+
type: boolean
|
|
376
|
+
description: Whether this is the latest version
|
|
377
|
+
metadata:
|
|
378
|
+
type: object
|
|
379
|
+
description: Additional metadata
|
|
380
|
+
'500':
|
|
381
|
+
description: Server error
|
|
382
|
+
|
|
383
|
+
/api/glossary/{langPair}/version/{glossaryId}/{versionId}:
|
|
384
|
+
get:
|
|
385
|
+
tags:
|
|
386
|
+
- Glossary Versioning
|
|
387
|
+
summary: Get a specific version of a glossary
|
|
388
|
+
parameters:
|
|
389
|
+
- in: path
|
|
390
|
+
name: langPair
|
|
391
|
+
schema:
|
|
392
|
+
type: string
|
|
393
|
+
required: true
|
|
394
|
+
description: The language pair in format 'xx-xx' (e.g., 'en-es')
|
|
395
|
+
- in: path
|
|
396
|
+
name: glossaryId
|
|
397
|
+
schema:
|
|
398
|
+
type: string
|
|
399
|
+
required: true
|
|
400
|
+
description: The glossary ID
|
|
401
|
+
- in: path
|
|
402
|
+
name: versionId
|
|
403
|
+
schema:
|
|
404
|
+
type: string
|
|
405
|
+
required: true
|
|
406
|
+
description: The S3 version ID
|
|
407
|
+
- in: query
|
|
408
|
+
name: name
|
|
409
|
+
schema:
|
|
410
|
+
type: string
|
|
411
|
+
description: Optional name of the glossary
|
|
412
|
+
responses:
|
|
413
|
+
'200':
|
|
414
|
+
description: Glossary version details
|
|
415
|
+
content:
|
|
416
|
+
application/json:
|
|
417
|
+
schema:
|
|
418
|
+
type: object
|
|
419
|
+
properties:
|
|
420
|
+
versionId:
|
|
421
|
+
type: string
|
|
422
|
+
description: S3 version ID
|
|
423
|
+
glossaryId:
|
|
424
|
+
type: string
|
|
425
|
+
description: Glossary ID
|
|
426
|
+
lastModified:
|
|
427
|
+
type: string
|
|
428
|
+
format: date-time
|
|
429
|
+
description: Version creation timestamp
|
|
430
|
+
metadata:
|
|
431
|
+
type: object
|
|
432
|
+
description: Additional metadata
|
|
433
|
+
'404':
|
|
434
|
+
description: Version not found
|
|
435
|
+
'500':
|
|
436
|
+
description: Server error
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import express from 'express';
|
|
7
|
+
|
|
8
|
+
// Create mock functions
|
|
9
|
+
const mockSend = vi.fn();
|
|
10
|
+
const mockS3Client = vi.fn();
|
|
11
|
+
|
|
12
|
+
// Mock AWS SDK modules
|
|
13
|
+
vi.mock('@aws-sdk/client-s3', () => {
|
|
14
|
+
mockS3Client.mockImplementation(() => ({ send: mockSend }));
|
|
15
|
+
return {
|
|
16
|
+
S3Client: mockS3Client,
|
|
17
|
+
PutObjectCommand: vi.fn(input => input),
|
|
18
|
+
GetObjectCommand: vi.fn(input => input)
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
vi.mock('@aws-sdk/s3-request-presigner', () => ({
|
|
23
|
+
getSignedUrl: vi.fn(() => Promise.resolve('https://mock-signed-url.com'))
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock s3Handler module
|
|
27
|
+
const mockGetManifest = vi.fn();
|
|
28
|
+
const mockUploadToS3 = vi.fn();
|
|
29
|
+
const mockSaveManifest = vi.fn();
|
|
30
|
+
|
|
31
|
+
vi.mock('../../s3Handler.js', () => ({
|
|
32
|
+
getManifest: mockGetManifest,
|
|
33
|
+
uploadToS3: mockUploadToS3,
|
|
34
|
+
saveManifest: mockSaveManifest,
|
|
35
|
+
resetS3Client: vi.fn()
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const TEST_FILES_DIR = path.join(__dirname, '..', 'test-files');
|
|
40
|
+
|
|
41
|
+
// Create test files directory if it doesn't exist
|
|
42
|
+
if (!fs.existsSync(TEST_FILES_DIR)) {
|
|
43
|
+
fs.mkdirSync(TEST_FILES_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create test files
|
|
47
|
+
const TEST_MOGRT_PATH = path.join(TEST_FILES_DIR, 'test.mogrt');
|
|
48
|
+
const TEST_PREVIEW_GIF_PATH = path.join(TEST_FILES_DIR, 'test.gif');
|
|
49
|
+
const TEST_PREVIEW_MP4_PATH = path.join(TEST_FILES_DIR, 'test.mp4');
|
|
50
|
+
|
|
51
|
+
fs.writeFileSync(TEST_MOGRT_PATH, 'test mogrt content');
|
|
52
|
+
fs.writeFileSync(TEST_PREVIEW_GIF_PATH, 'test preview gif content');
|
|
53
|
+
fs.writeFileSync(TEST_PREVIEW_MP4_PATH, 'test preview mp4 content');
|
|
54
|
+
|
|
55
|
+
describe('MOGRT Handler API Integration', () => {
|
|
56
|
+
let app;
|
|
57
|
+
|
|
58
|
+
beforeAll(async () => {
|
|
59
|
+
vi.setTimeout(15000); // Increase timeout for async operations
|
|
60
|
+
|
|
61
|
+
// Create express app
|
|
62
|
+
app = express();
|
|
63
|
+
|
|
64
|
+
// Set test AWS credentials
|
|
65
|
+
process.env.AWS_ACCESS_KEY_ID = 'test-key';
|
|
66
|
+
process.env.AWS_SECRET_ACCESS_KEY = 'test-secret';
|
|
67
|
+
process.env.AWS_REGION = 'us-east-1';
|
|
68
|
+
|
|
69
|
+
// Import and setup the handler
|
|
70
|
+
const { default: MogrtHandler } = await import('../../index.js');
|
|
71
|
+
app.all('/api/MogrtHandler', (req, res) => {
|
|
72
|
+
const context = {
|
|
73
|
+
log: vi.fn(),
|
|
74
|
+
res: {}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
MogrtHandler(context, req)
|
|
78
|
+
.then(() => {
|
|
79
|
+
res.status(context.res.status || 200).json(context.res.body);
|
|
80
|
+
})
|
|
81
|
+
.catch(err => {
|
|
82
|
+
res.status(500).json({ error: err.message });
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
vi.clearAllMocks();
|
|
89
|
+
|
|
90
|
+
// Default successful responses
|
|
91
|
+
mockGetManifest.mockImplementation(async (id) => ({
|
|
92
|
+
id: id === 'master' ? 'test-id' : id,
|
|
93
|
+
mogrtFile: 'test.mogrt',
|
|
94
|
+
previewFile: 'test.gif',
|
|
95
|
+
mogrtUrl: 'signed-url-1',
|
|
96
|
+
previewUrl: 'signed-url-2'
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
mockUploadToS3.mockImplementation((uploadId, fileData, filename) => {
|
|
100
|
+
return Promise.resolve({
|
|
101
|
+
key: `uploads/${uploadId}/${filename}`,
|
|
102
|
+
location: `https://test-bucket.s3.amazonaws.com/uploads/${uploadId}/${filename}`
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
mockSaveManifest.mockResolvedValue();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('GET /api/MogrtHandler', () => {
|
|
110
|
+
it('should return master manifest', async () => {
|
|
111
|
+
const response = await request(app)
|
|
112
|
+
.get('/api/MogrtHandler')
|
|
113
|
+
.expect('Content-Type', /json/)
|
|
114
|
+
.expect(200);
|
|
115
|
+
|
|
116
|
+
expect(mockGetManifest).toHaveBeenCalledWith('master');
|
|
117
|
+
expect(response.body).toEqual(expect.objectContaining({
|
|
118
|
+
id: 'test-id',
|
|
119
|
+
mogrtUrl: 'signed-url-1',
|
|
120
|
+
previewUrl: 'signed-url-2'
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should return specific manifest when manifestId provided', async () => {
|
|
125
|
+
const response = await request(app)
|
|
126
|
+
.get('/api/MogrtHandler?manifestId=specific-id')
|
|
127
|
+
.expect('Content-Type', /json/)
|
|
128
|
+
.expect(200);
|
|
129
|
+
|
|
130
|
+
expect(mockGetManifest).toHaveBeenCalledWith('specific-id');
|
|
131
|
+
expect(response.body).toEqual(expect.objectContaining({
|
|
132
|
+
id: 'specific-id',
|
|
133
|
+
mogrtUrl: 'signed-url-1',
|
|
134
|
+
previewUrl: 'signed-url-2'
|
|
135
|
+
}));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle manifest not found error', async () => {
|
|
139
|
+
const error = new Error('Manifest not found');
|
|
140
|
+
error.name = 'NoSuchKey';
|
|
141
|
+
mockGetManifest.mockRejectedValueOnce(error);
|
|
142
|
+
|
|
143
|
+
const response = await request(app)
|
|
144
|
+
.get('/api/MogrtHandler?manifestId=non-existent')
|
|
145
|
+
.expect('Content-Type', /json/)
|
|
146
|
+
.expect(500);
|
|
147
|
+
|
|
148
|
+
expect(response.body.error).toBe('Manifest not found');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('POST /api/MogrtHandler', () => {
|
|
153
|
+
test('should upload MOGRT and GIF preview files', async () => {
|
|
154
|
+
mockUploadToS3.mockResolvedValue({ key: 'test-key' });
|
|
155
|
+
mockSaveManifest.mockResolvedValue({});
|
|
156
|
+
|
|
157
|
+
const response = await request(app)
|
|
158
|
+
.post('/api/MogrtHandler')
|
|
159
|
+
.attach('mogrt', TEST_MOGRT_PATH)
|
|
160
|
+
.attach('preview', TEST_PREVIEW_GIF_PATH)
|
|
161
|
+
.expect(200);
|
|
162
|
+
|
|
163
|
+
expect(mockUploadToS3).toHaveBeenCalledTimes(2);
|
|
164
|
+
expect(mockSaveManifest).toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('should upload MOGRT and MP4 preview files', async () => {
|
|
168
|
+
mockUploadToS3.mockResolvedValue({ key: 'test-key' });
|
|
169
|
+
mockSaveManifest.mockResolvedValue({});
|
|
170
|
+
|
|
171
|
+
const response = await request(app)
|
|
172
|
+
.post('/api/MogrtHandler')
|
|
173
|
+
.attach('mogrt', TEST_MOGRT_PATH)
|
|
174
|
+
.attach('preview', TEST_PREVIEW_MP4_PATH)
|
|
175
|
+
.expect(200);
|
|
176
|
+
|
|
177
|
+
expect(mockUploadToS3).toHaveBeenCalledTimes(2);
|
|
178
|
+
expect(mockSaveManifest).toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('should reject invalid preview file type', async () => {
|
|
182
|
+
const TEST_INVALID_PATH = path.join(TEST_FILES_DIR, 'test.txt');
|
|
183
|
+
fs.writeFileSync(TEST_INVALID_PATH, 'invalid content');
|
|
184
|
+
|
|
185
|
+
const response = await request(app)
|
|
186
|
+
.post('/api/MogrtHandler')
|
|
187
|
+
.attach('mogrt', TEST_MOGRT_PATH)
|
|
188
|
+
.attach('preview', TEST_INVALID_PATH)
|
|
189
|
+
.expect(400);
|
|
190
|
+
|
|
191
|
+
expect(response.body.error).toContain('Invalid file type');
|
|
192
|
+
fs.unlinkSync(TEST_INVALID_PATH);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should reject upload with missing files', async () => {
|
|
196
|
+
const response = await request(app)
|
|
197
|
+
.post('/api/MogrtHandler')
|
|
198
|
+
.attach('mogrt', TEST_MOGRT_PATH)
|
|
199
|
+
.expect('Content-Type', /json/)
|
|
200
|
+
.expect(500);
|
|
201
|
+
|
|
202
|
+
expect(response.body.error).toMatch(/required/i);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('API Documentation', () => {
|
|
207
|
+
test('GET /docs should serve Swagger UI', async () => {
|
|
208
|
+
const response = await request(app)
|
|
209
|
+
.get('/docs')
|
|
210
|
+
.expect('Content-Type', /html/)
|
|
211
|
+
.expect(200);
|
|
212
|
+
|
|
213
|
+
// Swagger UI HTML should contain these key elements
|
|
214
|
+
expect(response.text).toContain('swagger-ui');
|
|
215
|
+
expect(response.text).toContain('MOGRT Handler API');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
afterAll(() => {
|
|
220
|
+
// Clean up test files
|
|
221
|
+
fs.unlinkSync(TEST_MOGRT_PATH);
|
|
222
|
+
fs.unlinkSync(TEST_PREVIEW_GIF_PATH);
|
|
223
|
+
fs.unlinkSync(TEST_PREVIEW_MP4_PATH);
|
|
224
|
+
fs.rmdirSync(TEST_FILES_DIR);
|
|
225
|
+
});
|
|
226
|
+
});
|