Skip to content

Commit 6d280b7

Browse files
committed
feat: implement get vudei encoding artifacts action
1 parent 8b435b7 commit 6d280b7

15 files changed

+327
-89
lines changed
Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
/* eslint-disable @typescript-eslint/naming-convention */
21
import { faker } from '@faker-js/faker';
32
import { expect, describe, it, beforeEach, afterEach } from 'vitest';
43

54
import { LoggerFactory } from '@common/logger';
65

76
import { ConfigFactory, type Config } from '../../config.js';
8-
import { RedisClientFactory, type RedisClient } from '@common/redis';
9-
import { ResourceNotFoundError } from '@common/errors';
107
import { GetVideoEncodingArtifactsAction } from './getVideoEncodingArtifactsAction.js';
8+
import { S3ClientFactory, S3Service } from '@common/s3';
9+
import { S3TestUtils } from '@common/s3/tests';
10+
import { bucketNames } from '@common/contracts';
11+
import { join, resolve } from 'node:path';
1112

1213
describe('GetVideoEncodingArtifactsAction', () => {
1314
let action: GetVideoEncodingArtifactsAction;
1415

1516
let config: Config;
1617

17-
let redisClient: RedisClient;
18+
let s3TestUtils: S3TestUtils;
19+
20+
const resourcesDirectory = resolve(__dirname, '../../../../../resources');
21+
22+
const sampleFileName = 'sample_video1.mp4';
1823

1924
beforeEach(async () => {
2025
config = ConfigFactory.create();
@@ -24,54 +29,75 @@ describe('GetVideoEncodingArtifactsAction', () => {
2429
logLevel: config.logLevel,
2530
});
2631

27-
redisClient = new RedisClientFactory(logger).create(config.redis);
32+
const s3Client = S3ClientFactory.create(config.aws);
33+
34+
const s3Service = new S3Service(s3Client);
35+
36+
action = new GetVideoEncodingArtifactsAction(s3Service, logger);
2837

29-
action = new GetVideoEncodingArtifactsAction(redisClient, logger);
38+
s3TestUtils = new S3TestUtils(s3Client);
3039

31-
await redisClient.flushall();
40+
await s3TestUtils.createBucket(bucketNames.encodingArtifacts);
3241
});
3342

3443
afterEach(async () => {
35-
await redisClient.flushall();
36-
37-
await redisClient.quit();
44+
await s3TestUtils.deleteBucket(bucketNames.encodingArtifacts);
3845
});
3946

4047
it('gets video encoding artifacts', async () => {
4148
const videoId = faker.string.uuid();
4249

43-
const redisKey = `${videoId}-encoding-progress`;
50+
const encoded360pBlobName = `${videoId}/360p`;
4451

45-
await redisClient.hset(redisKey, {
46-
'1080p': '75%',
47-
'720p': '55%',
48-
'480p': '30%',
49-
'360p': '100%',
50-
preview: '100%',
51-
preview_360p: '100%',
52-
preview_1080p: '3%',
53-
});
52+
const encodedPreviewBlobName = `${videoId}/preview`;
53+
54+
const encodedPreview360pBlobName = `${videoId}/preview_360p`;
55+
56+
const contentType = 'video/mp4';
5457

55-
const { encodedArtifacts } = await action.execute({ videoId });
58+
await s3TestUtils.uploadObject(
59+
bucketNames.encodingArtifacts,
60+
encoded360pBlobName,
61+
join(resourcesDirectory, sampleFileName),
62+
contentType,
63+
);
64+
65+
await s3TestUtils.uploadObject(
66+
bucketNames.encodingArtifacts,
67+
encodedPreviewBlobName,
68+
join(resourcesDirectory, sampleFileName),
69+
contentType,
70+
);
71+
72+
await s3TestUtils.uploadObject(
73+
bucketNames.encodingArtifacts,
74+
encodedPreview360pBlobName,
75+
join(resourcesDirectory, sampleFileName),
76+
contentType,
77+
);
78+
79+
const { encodingArtifacts } = await action.execute({ videoId });
80+
81+
encodingArtifacts.every((artifact) => {
82+
expect(artifact.url).toContain(bucketNames.encodingArtifacts);
83+
84+
expect(artifact.url).toContain(videoId);
85+
86+
expect(['360p', 'preview', 'preview_360p'].some((id) => artifact.url.includes(id))).toBe(true);
87+
});
5688

5789
expect(encodingArtifacts).toEqual([
58-
{ id: '1080p', progress: '75%' },
59-
{ id: '720p', progress: '55%' },
60-
{ id: '480p', progress: '30%' },
61-
{ id: '360p', progress: '10%' },
62-
{ id: 'preview', progress: '0%' },
63-
{ id: 'preview_360p', progress: '1%' },
64-
{ id: 'preview_1080p', progress: '3%' },
90+
{ id: '360p', url: expect.any(String), contentType },
91+
{ id: 'preview', url: expect.any(String), contentType },
92+
{ id: 'preview_360p', url: expect.any(String), contentType },
6593
]);
6694
});
6795

68-
it('throws error if video artifacts not found', async () => {
96+
it('returns empty array if video artifacts not found', async () => {
6997
const videoId = faker.string.uuid();
7098

71-
try {
72-
await action.execute({ videoId });
73-
} catch (error) {
74-
expect(error).toBeInstanceOf(ResourceNotFoundError);
75-
}
99+
const result = await action.execute({ videoId });
100+
101+
expect(result.encodingArtifacts).toHaveLength(0);
76102
});
77103
});
Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
import { ResourceNotFoundError } from '@common/errors';
1+
import { bucketNames } from '@common/contracts';
22
import { type Logger } from '@common/logger';
3-
import { type RedisClient } from '@common/redis';
3+
import { type S3Service } from '@common/s3';
44

55
export interface GetVideoEncodingArtifactsActionPayload {
66
readonly videoId: string;
77
}
88

99
export interface GetVideoEncodingArtifactsActionResult {
10-
readonly encodingArtifacts: { id: string; url: string }[];
10+
readonly encodingArtifacts: {
11+
readonly id: string;
12+
readonly contentType: string;
13+
readonly url: string;
14+
}[];
1115
}
1216

1317
export class GetVideoEncodingArtifactsAction {
1418
public constructor(
15-
private readonly redisClient: RedisClient,
19+
private readonly s3Service: S3Service,
1620
private readonly logger: Logger,
1721
) {}
1822

@@ -23,33 +27,28 @@ export class GetVideoEncodingArtifactsAction {
2327

2428
this.logger.debug({
2529
message: 'Fetching video encoding artifacts...',
30+
bucketName: bucketNames.encodingArtifacts,
2631
videoId,
2732
});
2833

29-
const redisKey = `${videoId}-encoding-progress`;
30-
31-
const encodingProgress = await this.redisClient.hgetall(redisKey);
32-
33-
if (!encodingProgress) {
34-
throw new ResourceNotFoundError({
35-
resource: 'EncodingProgress',
36-
id: videoId,
37-
});
38-
}
39-
40-
const encodedArtifacts = Object.entries(encodingProgress)
41-
.map(([id, progress]) => ({
42-
id,
43-
progress,
44-
}))
45-
.filter(({ progress }) => progress === '100%');
34+
const encodingArtifacts = await this.s3Service.getBlobsUrls({
35+
bucketName: bucketNames.encodingArtifacts,
36+
prefix: videoId,
37+
});
4638

4739
this.logger.debug({
4840
message: 'Video encoding artifacts fetched.',
4941
videoId,
50-
encodedArtifacts: encodedArtifacts.map(({ id }) => id),
42+
bucketName: bucketNames.encodingArtifacts,
43+
count: encodingArtifacts.length,
5144
});
5245

53-
return { encodingArtifacts: flatEncodingArtifacts };
46+
return {
47+
encodingArtifacts: encodingArtifacts.map(({ name, url, contentType }) => ({
48+
id: name.replace(`${videoId}/`, ''),
49+
url,
50+
contentType,
51+
})),
52+
};
5453
}
5554
}

apps/api/src/actions/getVideoEncodingProgressAction/getVideoEncodingProgressAction.integration.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ describe('GetVideoEncodingProgressAction', () => {
7272
await action.execute({ videoId });
7373
} catch (error) {
7474
expect(error).toBeInstanceOf(ResourceNotFoundError);
75+
76+
return;
7577
}
78+
79+
expect.fail();
7680
});
7781
});

apps/api/src/actions/getVideoEncodingProgressAction/getVideoEncodingProgressAction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class GetVideoEncodingProgressAction {
2828

2929
const encodingProgress = await this.redisClient.hgetall(redisKey);
3030

31-
if (!encodingProgress) {
31+
if (Object.keys(encodingProgress).length === 0) {
3232
throw new ResourceNotFoundError({
3333
resource: 'EncodingProgress',
3434
id: videoId,

apps/api/src/actions/uploadVideoAction/uploadVideoAction.integration.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { faker } from '@faker-js/faker';
22
import { randomUUID } from 'node:crypto';
33
import { createReadStream } from 'node:fs';
4-
import path from 'path';
54
import { expect, describe, it, beforeEach, afterEach } from 'vitest';
5+
import { resolve, join } from 'node:path';
66

77
import { LoggerFactory } from '@common/logger';
88
import { S3ClientFactory, S3Service } from '@common/s3';
@@ -26,11 +26,11 @@ describe('UploadVideoAction', () => {
2626

2727
let amqpChannel: Channel;
2828

29-
const resourcesDirectory = path.resolve(__dirname, '../../../../../resources');
29+
const resourcesDirectory = resolve(__dirname, '../../../../../resources');
3030

3131
const sampleFileName = 'sample_video1.mp4';
3232

33-
const filePath = path.join(resourcesDirectory, sampleFileName);
33+
const filePath = join(resourcesDirectory, sampleFileName);
3434

3535
const videoId = randomUUID();
3636

@@ -109,7 +109,7 @@ describe('UploadVideoAction', () => {
109109
expect(parsedMessage).toEqual({
110110
videoId,
111111
userEmail,
112-
url: `http://transcoder-ingested-videos.s3.localhost.localstack.cloud:4566/${videoId}`,
112+
url: `http://${bucketNames.ingestedVideos}.s3.localhost.localstack.cloud:4566/${videoId}`,
113113
});
114114
});
115115
});

common/amqp/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
],
1818
"scripts": {
1919
"build": "tsc --build tsconfig.json",
20-
"build:dev": "tsc --build tsconfig.json"
20+
"build:dev": "tsc --build tsconfig.json",
21+
"lint": "eslint . -c ../../eslint.config.mjs --max-warnings 0",
22+
"lint:fix": "npm run lint -- --fix"
2123
},
2224
"dependencies": {
2325
"@common/logger": "*",

common/contracts/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
],
1414
"scripts": {
1515
"build": "tsc --build tsconfig.json",
16-
"build:dev": "tsc --build tsconfig.json"
16+
"build:dev": "tsc --build tsconfig.json",
17+
"lint": "eslint . -c ../../eslint.config.mjs --max-warnings 0",
18+
"lint:fix": "npm run lint -- --fix"
1719
},
1820
"volta": {
1921
"node": "20.17.0",

common/errors/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
],
1414
"scripts": {
1515
"build": "tsc --build tsconfig.json",
16-
"build:dev": "tsc --build tsconfig.json"
16+
"build:dev": "tsc --build tsconfig.json",
17+
"lint": "eslint . -c ../../eslint.config.mjs --max-warnings 0",
18+
"lint:fix": "npm run lint -- --fix"
1719
},
1820
"volta": {
1921
"node": "20.17.0",

common/redis/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
],
1818
"scripts": {
1919
"build": "tsc --build tsconfig.json",
20-
"build:dev": "tsc --build tsconfig.json"
20+
"build:dev": "tsc --build tsconfig.json",
21+
"lint": "eslint . -c ../../eslint.config.mjs --max-warnings 0",
22+
"lint:fix": "npm run lint -- --fix"
2123
},
2224
"dependencies": {
2325
"@common/logger": "*",

common/s3/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
],
1818
"scripts": {
1919
"build": "tsc --build tsconfig.prod.json",
20-
"build:dev": "tsc --build tsconfig.json"
20+
"build:dev": "tsc --build tsconfig.json",
21+
"test:integration": "NODE_ENV=test vitest --config vitest.integration.config.js .integration.test.ts",
22+
"test:integration:run": "npm run test:integration -- --run",
23+
"lint": "eslint . -c ../../eslint.config.mjs --max-warnings 0",
24+
"lint:fix": "npm run lint -- --fix"
2125
},
2226
"dependencies": {
2327
"@aws-sdk/client-s3": "3.592.0",

0 commit comments

Comments
 (0)