Skip to content

Commit 061b339

Browse files
authored
#17 - adding tests for syncer (#33)
* #17 - adding tests for syncer * Reslove review comments * Reslove review comments
1 parent f8e191a commit 061b339

File tree

8 files changed

+1201
-690
lines changed

8 files changed

+1201
-690
lines changed

.github/workflows/lambda-runner-binaries-syncer.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@ jobs:
2424
run: yarn install
2525
- name: Run linter
2626
run: yarn lint
27+
- name: Run tests
28+
run: yarn test
2729
- name: Build distribution
2830
run: yarn build

modules/runner-binaries-syncer/lambdas/runner-binaries-syncer/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
},
1515
"devDependencies": {
1616
"@octokit/rest": "^17.6.0",
17-
"@types/jest": "^25.2.1",
17+
"@types/jest": "^25.2.3",
1818
"@types/node": "^13.13.4",
1919
"@types/request": "^2.48.4",
2020
"@typescript-eslint/eslint-plugin": "^2.30.0",
2121
"@typescript-eslint/parser": "^2.30.0",
2222
"@zeit/ncc": "^0.22.1",
23-
"aws-sdk": "^2.645.0",
23+
"aws-sdk": "^2.671.0",
2424
"eslint": "^6.8.0",
25-
"jest": "^25.4.0",
26-
"ts-jest": "^25.4.0",
25+
"jest": "^26.0.1",
26+
"ts-jest": "^26.0.0",
2727
"ts-node-dev": "^1.0.0-pre.44",
2828
"typescript": "^3.8.3"
2929
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { handle } from './handler';
2+
import latestReleases from '../../test/resources/github-latest-releases.json';
3+
import latestReleasesEmpty from '../../test/resources/github-latest-releases-empty.json';
4+
import latestReleasesNoLinux from '../../test/resources/github-latest-releases-no-linux.json';
5+
6+
const mockOctokit = {
7+
repos: {
8+
getLatestRelease: jest.fn(),
9+
},
10+
};
11+
jest.mock('@octokit/rest', () => ({
12+
Octokit: jest.fn().mockImplementation(() => mockOctokit),
13+
}));
14+
15+
const mockS3 = {
16+
getObjectTagging: jest.fn(),
17+
upload: jest.fn(),
18+
};
19+
jest.mock('aws-sdk', () => ({
20+
S3: jest.fn().mockImplementation(() => mockS3),
21+
}));
22+
23+
const bucketName = 'my-bucket';
24+
const bucketObjectKey = 'actions-runner-linux.tar.gz';
25+
beforeEach(() => {
26+
jest.clearAllMocks();
27+
});
28+
29+
describe('Synchronize action distribution.', () => {
30+
beforeEach(() => {
31+
process.env.S3_BUCKET_NAME = bucketName;
32+
process.env.S3_OBJECT_KEY = bucketObjectKey;
33+
34+
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
35+
data: latestReleases.data,
36+
}));
37+
});
38+
39+
it('Distribution is up-to-date.', async () => {
40+
mockS3.getObjectTagging.mockImplementation(() => {
41+
return {
42+
promise() {
43+
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-2.262.1.tar.gz' }] });
44+
},
45+
};
46+
});
47+
48+
await handle();
49+
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
50+
expect(mockS3.getObjectTagging).toBeCalledWith({
51+
Bucket: bucketName,
52+
Key: bucketObjectKey,
53+
});
54+
expect(mockS3.upload).toBeCalledTimes(0);
55+
});
56+
57+
it('Distribution should update.', async () => {
58+
mockS3.getObjectTagging.mockImplementation(() => {
59+
return {
60+
promise() {
61+
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-0.tar.gz' }] });
62+
},
63+
};
64+
});
65+
66+
await handle();
67+
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
68+
expect(mockS3.getObjectTagging).toBeCalledWith({
69+
Bucket: bucketName,
70+
Key: bucketObjectKey,
71+
});
72+
expect(mockS3.upload).toBeCalledTimes(1);
73+
});
74+
75+
it('No tag in S3, distribution should update.', async () => {
76+
mockS3.getObjectTagging.mockImplementation(() => {
77+
return {
78+
promise() {
79+
throw new Error();
80+
},
81+
};
82+
});
83+
84+
await handle();
85+
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
86+
expect(mockS3.getObjectTagging).toBeCalledWith({
87+
Bucket: bucketName,
88+
Key: bucketObjectKey,
89+
});
90+
expect(mockS3.upload).toBeCalledTimes(1);
91+
});
92+
93+
it('Tags, but no version, distribution should update.', async () => {
94+
mockS3.getObjectTagging.mockImplementation(() => {
95+
return {
96+
promise() {
97+
return Promise.resolve({ TagSet: [{ Key: 'someKey', Value: 'someValue' }] });
98+
},
99+
};
100+
});
101+
102+
await handle();
103+
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
104+
expect(mockS3.getObjectTagging).toBeCalledWith({
105+
Bucket: bucketName,
106+
Key: bucketObjectKey,
107+
});
108+
expect(mockS3.upload).toBeCalledTimes(1);
109+
});
110+
});
111+
112+
describe('No release assets found.', () => {
113+
const errorMessage = 'Cannot find GitHub release asset.';
114+
beforeEach(() => {
115+
process.env.S3_BUCKET_NAME = bucketName;
116+
process.env.S3_OBJECT_KEY = bucketObjectKey;
117+
});
118+
119+
it('Empty list of assets.', async () => {
120+
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
121+
data: latestReleasesEmpty.data,
122+
}));
123+
124+
await expect(handle()).rejects.toThrow(errorMessage);
125+
});
126+
127+
it('No linux x64 asset.', async () => {
128+
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
129+
data: latestReleasesNoLinux.data,
130+
}));
131+
132+
await expect(handle()).rejects.toThrow(errorMessage);
133+
});
134+
});
135+
136+
describe('Invalid config', () => {
137+
const errorMessage = 'Please check all mandatory variables are set.';
138+
it('No bucket and object key.', async () => {
139+
delete process.env.S3_OBJECT_KEY;
140+
delete process.env.S3_BUCKET_NAME;
141+
await expect(handle()).rejects.toThrow(errorMessage);
142+
});
143+
it('No bucket.', async () => {
144+
delete process.env.S3_BUCKET_NAME;
145+
process.env.S3_OBJECT_KEY = bucketObjectKey;
146+
await expect(handle()).rejects.toThrow(errorMessage);
147+
});
148+
it('No object key.', async () => {
149+
delete process.env.S3_OBJECT_KEY;
150+
process.env.S3_BUCKET_NAME = bucketName;
151+
await expect(handle()).rejects.toThrow(errorMessage);
152+
});
153+
});

modules/runner-binaries-syncer/lambdas/runner-binaries-syncer/src/syncer/handler.ts

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,19 @@ import request from 'request';
44
import { S3 } from 'aws-sdk';
55
import AWS from 'aws-sdk';
66

7-
AWS.config.update({
8-
region: process.env.AWS_REGION,
9-
});
10-
const s3 = new S3();
11-
127
const versionKey = 'name';
13-
const bucketName = process.env.S3_BUCKET_NAME as string;
14-
const bucketObjectKey = process.env.S3_OBJECT_KEY as string;
15-
if (!bucketName || !bucketObjectKey) {
16-
throw new Error('Please check all mandatory variables are set.');
8+
9+
interface CacheObject {
10+
bucket: string;
11+
key: string;
1712
}
1813

19-
async function getCachedVersion(): Promise<string | undefined> {
14+
async function getCachedVersion(s3: S3, cacheObject: CacheObject): Promise<string | undefined> {
2015
try {
2116
const objectTagging = await s3
2217
.getObjectTagging({
23-
Bucket: bucketName,
24-
Key: bucketObjectKey,
18+
Bucket: cacheObject.bucket,
19+
Key: cacheObject.key,
2520
})
2621
.promise();
2722
const versions = objectTagging.TagSet?.filter((t: S3.Tag) => t.Key === versionKey);
@@ -31,30 +26,29 @@ async function getCachedVersion(): Promise<string | undefined> {
3126
return undefined;
3227
}
3328
}
34-
3529
interface ReleaseAsset {
3630
name: string;
3731
downloadUrl: string;
3832
}
3933

4034
async function getLinuxReleaseAsset(): Promise<ReleaseAsset | undefined> {
4135
const githubClient = new Octokit();
42-
const linuxAssets = (
43-
await githubClient.repos.getLatestRelease({
44-
owner: 'actions',
45-
repo: 'runner',
46-
})
47-
).data.assets.filter((a) => a.name?.includes('actions-runner-linux-x64-'));
36+
const assets = await githubClient.repos.getLatestRelease({
37+
owner: 'actions',
38+
repo: 'runner',
39+
});
40+
const linuxAssets = assets.data.assets?.filter((a) => a.name?.includes('actions-runner-linux-x64-'));
41+
4842
return linuxAssets?.length === 1
4943
? { name: linuxAssets[0].name, downloadUrl: linuxAssets[0].browser_download_url }
5044
: undefined;
5145
}
5246

53-
async function uploadToS3(actionRunnerReleaseAsset: ReleaseAsset): Promise<void> {
47+
async function uploadToS3(s3: S3, cacheObject: CacheObject, actionRunnerReleaseAsset: ReleaseAsset): Promise<void> {
5448
const writeStream = new PassThrough();
5549
s3.upload({
56-
Bucket: bucketName,
57-
Key: bucketObjectKey,
50+
Bucket: cacheObject.bucket,
51+
Key: cacheObject.key,
5852
Tagging: versionKey + '=' + actionRunnerReleaseAsset.name,
5953
Body: writeStream,
6054
}).promise();
@@ -77,15 +71,25 @@ async function uploadToS3(actionRunnerReleaseAsset: ReleaseAsset): Promise<void>
7771
}
7872

7973
export const handle = async (): Promise<void> => {
74+
const s3 = new AWS.S3();
75+
76+
const cacheObject: CacheObject = {
77+
bucket: process.env.S3_BUCKET_NAME as string,
78+
key: process.env.S3_OBJECT_KEY as string,
79+
};
80+
if (!cacheObject.bucket || !cacheObject.key) {
81+
throw Error('Please check all mandatory variables are set.');
82+
}
83+
8084
const actionRunnerReleaseAsset = await getLinuxReleaseAsset();
8185
if (actionRunnerReleaseAsset === undefined) {
82-
throw Error('Cannot find github release asset.');
86+
throw Error('Cannot find GitHub release asset.');
8387
}
8488

85-
const currentVersion = await getCachedVersion();
86-
console.log('latest: ' + currentVersion);
89+
const currentVersion = await getCachedVersion(s3, cacheObject);
90+
console.debug('latest: ' + currentVersion);
8791
if (currentVersion === undefined || currentVersion != actionRunnerReleaseAsset.name) {
88-
uploadToS3(actionRunnerReleaseAsset);
92+
uploadToS3(s3, cacheObject, actionRunnerReleaseAsset);
8993
} else {
9094
console.debug('Distribution is up-to-date, no action.');
9195
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"status": 200,
3+
"url": "https://api.github.com/repos/actions/runner/releases/latest",
4+
"data": {
5+
"tag_name": "v2.262.1",
6+
"name": "v2.262.1"
7+
}
8+
}

0 commit comments

Comments
 (0)