Skip to content

Commit 18edb62

Browse files
committed
Add download unit tests
1 parent 4d5ccb6 commit 18edb62

File tree

9 files changed

+296
-36
lines changed

9 files changed

+296
-36
lines changed

downloadCurrentVersion.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,25 @@
55

66
import downloader from './downloader';
77
const pkg = require('./package.json');
8+
const BIN_URL = 'https://binaries.soliditylang.org/bin';
89

9-
async function download () {
10+
async function download (version, binURL = BIN_URL) {
1011
try {
11-
const list = JSON.parse(await downloader.getVersionList());
12-
const wanted = pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1];
13-
const releaseFileName = list.releases[wanted];
12+
const list = JSON.parse(await downloader.getVersionList(`${binURL}/list.json`));
13+
const releaseFileName = list.releases[version];
1414
const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0];
15+
1516
if (!expectedFile) {
1617
throw new Error('Requested version not found. Version list is invalid or corrupted?');
1718
}
19+
1820
const expectedHash = expectedFile.keccak256;
19-
await downloader.downloadBinary('soljson.js', releaseFileName, expectedHash);
20-
process.exit();
21+
await downloader.downloadBinary(binURL, 'soljson.js', releaseFileName, expectedHash);
2122
} catch (err) {
2223
console.log(err.message);
2324
process.exit(1);
2425
}
25-
}
26+
};
2627

2728
console.log('Downloading correct solidity binary...');
28-
download();
29+
download(pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1]);

downloader.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { https } from 'follow-redirects';
33
import MemoryStream from 'memorystream';
44
import { keccak256 } from 'js-sha3';
55

6-
function getVersionList () {
6+
function getVersionList (url: string): Promise<string> {
77
console.log('Retrieving available version list...');
88

99
return new Promise<string>((resolve, reject) => {
1010
const mem = new MemoryStream(null, { readable: false });
11-
https.get('https://binaries.soliditylang.org/bin/list.json', function (response) {
11+
https.get(url, function (response) {
1212
if (response.statusCode !== 200) {
1313
reject(new Error('Error downloading file: ' + response.statusCode));
1414
}
@@ -22,8 +22,8 @@ function getVersionList () {
2222
});
2323
}
2424

25-
function downloadBinary (outputName, version, expectedHash) {
26-
console.log('Downloading version', version);
25+
function downloadBinary (binURL: string, outputName: string, releaseFile: string, expectedHash: string): Promise<void> {
26+
console.log('Downloading version', releaseFile);
2727

2828
return new Promise<void>((resolve, reject) => {
2929
// Remove if existing
@@ -37,7 +37,7 @@ function downloadBinary (outputName, version, expectedHash) {
3737
});
3838

3939
const file = fs.createWriteStream(outputName, { encoding: 'binary' });
40-
https.get('https://binaries.soliditylang.org/bin/' + version, function (response) {
40+
https.get(`${binURL}/${releaseFile}`, function (response) {
4141
if (response.statusCode !== 200) {
4242
reject(new Error('Error downloading file: ' + response.statusCode));
4343
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"eslint-plugin-import": "^2.25.3",
7575
"eslint-plugin-node": "^11.1.0",
7676
"eslint-plugin-promise": "^5.1.1",
77+
"nock": "^13.2.9",
7778
"nyc": "^15.1.0",
7879
"tape": "^4.11.0",
7980
"tape-spawn": "^1.4.2",

test/downloader.ts

Lines changed: 175 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,190 @@
1-
import tape from 'tape';
2-
import * as semver from 'semver';
31
import * as tmp from 'tmp';
4-
import wrapper from '../wrapper';
2+
import tape from 'tape';
3+
import nock from 'nock';
4+
import fs from 'fs';
5+
import path from 'path';
6+
import { keccak256 } from 'js-sha3';
7+
import { https } from 'follow-redirects';
58
import downloader from '../downloader';
69

7-
const pkg = require('../package.json');
10+
const assets = path.resolve(__dirname, 'resources/assets');
11+
12+
tape.onFinish(() => {
13+
if (!nock.isDone()) {
14+
throw Error('expected requests were not performed');
15+
}
16+
});
17+
18+
function hash (filePath: string): string {
19+
return '0x' + keccak256(fs.readFileSync(filePath, { encoding: 'binary' }));
20+
}
21+
22+
function generateTestFile (t: tape.Test, content: string): tmp.FileResult {
23+
// As the `keep` option is set to true the removeCallback must be called by the caller
24+
// to cleanup the files after the test.
25+
const file = tmp.fileSync({ template: 'soljson-XXXXXX.js', keep: true });
26+
try {
27+
fs.writeFileSync(file.name, content);
28+
} catch (err) {
29+
t.fail('error writing test file');
30+
}
31+
32+
return file;
33+
}
34+
35+
function versionListMock (url: string): nock.Interceptor {
36+
return nock(url).get('/bin/list.json');
37+
}
38+
39+
function downloadBinaryMock (url: string, filename: string): nock.Interceptor {
40+
return nock(url).get(`/bin/${path.basename(filename)}`);
41+
}
42+
43+
function defaultListener (req, res) {
44+
res.writeHead(200);
45+
res.end('OK');
46+
};
47+
48+
async function startMockServer (listener = defaultListener) {
49+
const server = https.createServer({
50+
key: fs.readFileSync(path.resolve(assets, 'key.pem')),
51+
cert: fs.readFileSync(path.resolve(assets, 'cert.pem'))
52+
}, listener);
53+
54+
await new Promise(resolve => server.listen(resolve));
55+
server.port = server.address().port;
56+
server.origin = `https://localhost:${server.port}`;
57+
return server;
58+
}
59+
60+
tape('Download version list', async function (t) {
61+
const server = await startMockServer();
62+
63+
t.teardown(function () {
64+
server.close();
65+
nock.cleanAll();
66+
});
67+
68+
t.test('successfully get version list', async function (st) {
69+
const dummyListPath = path.resolve(assets, 'dummy-list.json');
70+
versionListMock(server.origin).replyWithFile(200, dummyListPath, {
71+
'Content-Type': 'application/json'
72+
});
873

9-
tape('Download latest binary', function (t) {
10-
t.test('checking whether the current version is the latest available for download', async function (st) {
1174
try {
12-
const list = JSON.parse(await downloader.getVersionList());
13-
const wanted = pkg.version.match(/^(\d+\.\d+\.\d+)$/)[1];
14-
if (semver.neq(wanted, list.latestRelease)) {
15-
st.fail(`Version ${wanted} is not the latest release ${list.latestRelease}`);
16-
}
75+
const list = JSON.parse(
76+
await downloader.getVersionList(`${server.origin}/bin/list.json`)
77+
);
78+
const expected = require(dummyListPath);
79+
st.deepEqual(list, expected, 'list should match');
80+
st.equal(list.latestRelease, expected.latestRelease, 'latest release should be equal');
81+
} catch (err) {
82+
st.fail(err.message);
83+
}
84+
st.end();
85+
});
1786

18-
const releaseFileName = list.releases[wanted];
19-
const expectedFile = list.builds.filter(function (entry) { return entry.path === releaseFileName; })[0];
20-
if (!expectedFile) {
21-
st.fail(`Version ${wanted} not found. Version list is invalid or corrupted?`);
22-
}
87+
t.test('should throw an exception when version list not found', async function (st) {
88+
versionListMock(server.origin).reply(404);
89+
90+
try {
91+
await downloader.getVersionList(`${server.origin}/bin/list.json`);
92+
st.fail('should throw file not found error');
93+
} catch (err) {
94+
st.equal(err.message, 'Error downloading file: 404', 'should throw file not found error');
95+
}
96+
st.end();
97+
});
98+
});
99+
100+
tape('Download latest binary', async function (t) {
101+
const server = await startMockServer();
102+
const content = '() => {}';
103+
const tmpDir = tmp.dirSync({ unsafeCleanup: true, prefix: 'solcjs-download-test-' }).name;
104+
105+
t.teardown(function () {
106+
server.close();
107+
nock.cleanAll();
108+
});
109+
110+
t.test('successfully download binary', async function (st) {
111+
const targetFilename = `${tmpDir}/target-success.js`;
112+
const file = generateTestFile(st, content);
23113

24-
const tempDir = tmp.dirSync({ unsafeCleanup: true, prefix: 'solc-js-compiler-test-' }).name;
25-
const solcjsBin = `${tempDir}/${expectedFile.path}`;
26-
await downloader.downloadBinary(solcjsBin, releaseFileName, expectedFile.keccak256);
114+
st.teardown(function () {
115+
file.removeCallback();
116+
});
27117

28-
const solc = wrapper(require(solcjsBin));
29-
if (semver.neq(solc.version(), wanted)) {
30-
st.fail('Downloaded version differs from package version');
118+
downloadBinaryMock(server.origin, file.name)
119+
.replyWithFile(200, file.name, {
120+
'content-type': 'application/javascript',
121+
'content-length': content.length.toString()
122+
});
123+
124+
try {
125+
await downloader.downloadBinary(
126+
`${server.origin}/bin`,
127+
targetFilename,
128+
file.name,
129+
hash(file.name)
130+
);
131+
132+
if (!fs.existsSync(targetFilename)) {
133+
st.fail('download failed');
31134
}
32-
st.pass(`Version ${wanted} successfully downloaded`);
135+
136+
const got = fs.readFileSync(targetFilename, { encoding: 'binary' });
137+
const expected = fs.readFileSync(file.name, { encoding: 'binary' });
138+
st.equal(got.length, expected.length, 'should download the correct file');
33139
} catch (err) {
34140
st.fail(err.message);
35141
}
36142
st.end();
37143
});
144+
145+
t.test('should throw an exception when file not found', async function (st) {
146+
const targetFilename = `${tmpDir}/target-fail404.js`;
147+
downloadBinaryMock(server.origin, 'test.js').reply(404);
148+
149+
try {
150+
await downloader.downloadBinary(
151+
`${server.origin}/bin`,
152+
targetFilename,
153+
'test.js',
154+
`0x${keccak256('something')}`
155+
);
156+
st.fail('should throw file not found error');
157+
} catch (err) {
158+
st.equal(err.message, 'Error downloading file: 404', 'should throw file not found error');
159+
}
160+
st.end();
161+
});
162+
163+
t.test('should throw an exception if hashes do not match', async function (st) {
164+
const targetFilename = `${tmpDir}/target-fail-hash.js`;
165+
const file = generateTestFile(st, content);
166+
167+
st.teardown(function () {
168+
file.removeCallback();
169+
});
170+
171+
downloadBinaryMock(server.origin, file.name)
172+
.replyWithFile(200, file.name, {
173+
'content-type': 'application/javascript',
174+
'content-length': content.length.toString()
175+
});
176+
177+
try {
178+
await downloader.downloadBinary(
179+
`${server.origin}/bin`,
180+
targetFilename,
181+
file.name,
182+
`0x${keccak256('something')}`
183+
);
184+
st.fail('should throw hash mismatch error');
185+
} catch (err) {
186+
st.match(err.message, /Hash mismatch/, 'should detect hash mismatch');
187+
}
188+
st.end();
189+
});
38190
});

test/resources/assets/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
The certificates in this folder are only for testing purposes and are **not** valid certificates.
2+
3+
They were generated using the following command:
4+
- Generate an RSA private key:
5+
`openssl genrsa -out key.pem`
6+
- Generate a csr using all default options and common name as "localhost":
7+
`openssl req -new -key key.pem -out csr.pem`
8+
- Self-sign the certificate:
9+
`openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem`

test/resources/assets/cert.pem

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDOzCCAiMCFHpFewtcvLRCZbPPcl04e2vNaFtBMA0GCSqGSIb3DQEBCwUAMFkx
3+
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
4+
cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yMjA5
5+
MTYxNjExMzRaGA8yMDUwMDEzMTE2MTEzNFowWTELMAkGA1UEBhMCQVUxEzARBgNV
6+
BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
7+
ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
8+
CgKCAQEAqx1uPfkuqDqphLOwAMhvQqYWec7037WOrpO/5mHees9fVAX2QyGxV+Dg
9+
zTCHJ4Jn4ATkw3i4yfgEwa4cuHhBuopmK7WCR3F0x8HLR6XA3nupWUbplT6L8BqH
10+
9YOrT8XRnbQuIV5+y7p9AkTh4WaLFQXUfnlU9Pvu3qIp9eYt1QDMY9NVF2IMPXsk
11+
2CigcqwBPVya6PHNbPYZiEW7B6Hh+rIk1yoG/bNdh3ZCLPBjX6tfpmxH8KZgfAZ4
12+
Q83naav7ABoTL2FQIwwMBKYinvVkzissY/s9cwDmSS3q1pc1CIRLwjuEr9OfdUIk
13+
JNYqM6Bu2J8mpL3tyJTMTsx6OdtrNQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCT
14+
X/hdb5S7s4Nzg8m/DsfaazftqwAtJVP7ZhXDewJ9/QKpA85mqcQDAZg+1EOVXRmW
15+
VVSrEeOPm88T1y/aNBEYOL86koKHmvu5KwVvkVZGbebDKdSljdp8B1FEncGeQjXF
16+
3lusP8DLCkzR0Lk4fORiVn9cdbgo8qM5rQMlF2DOJSVXgtEsp6HUJ3CZ9kdJ8QHO
17+
/nL5R1ADTIkQHL73JmIxrAU6GQdMU0YqYybaZa0uOcYHBm8F9c/nGRj2Whm8SQ84
18+
WOA9cA/vdbdqSr80LOQ2kvx9Na9tGBRvOaHZT3q6CfysmQdvoogqLFLIwof8Sz2O
19+
PfL6Z1Y7lAFnd+ThVK/z
20+
-----END CERTIFICATE-----

test/resources/assets/csr.pem

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-----BEGIN CERTIFICATE REQUEST-----
2+
MIICnjCCAYYCAQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
3+
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9j
4+
YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqx1uPfkuqDqp
5+
hLOwAMhvQqYWec7037WOrpO/5mHees9fVAX2QyGxV+DgzTCHJ4Jn4ATkw3i4yfgE
6+
wa4cuHhBuopmK7WCR3F0x8HLR6XA3nupWUbplT6L8BqH9YOrT8XRnbQuIV5+y7p9
7+
AkTh4WaLFQXUfnlU9Pvu3qIp9eYt1QDMY9NVF2IMPXsk2CigcqwBPVya6PHNbPYZ
8+
iEW7B6Hh+rIk1yoG/bNdh3ZCLPBjX6tfpmxH8KZgfAZ4Q83naav7ABoTL2FQIwwM
9+
BKYinvVkzissY/s9cwDmSS3q1pc1CIRLwjuEr9OfdUIkJNYqM6Bu2J8mpL3tyJTM
10+
Tsx6OdtrNQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAH49b7ktMCNIScGNIgy1
11+
XeF+sUrXtOYv67vhIPxF8y9ep7Lm6nHOGTzEmoHqf0ogHTb4cGclgakdaRAH65eU
12+
BZvIaxEUFdV7G787ESkXdyvtvZB6cc6/ZJSUPsnpoGkS08kNrzZBXfMBapoawyTN
13+
g31zyerxjRWTEKEBB7u74cTyamjNEgml0cOa7VB6CkPI9PM2/VkYLCpqZNLTbzSE
14+
GHXzouzE7ajZ4dxx3aEsXXjZLypWimjZJA3zcvl9Jv1p6zEkDwnjofOcGiIDmdgu
15+
sdlj3v9Efv/0A03Q7z/rr/Gl83lskFQB5VTU1DBrkFJ4D6VueWjwBrqff3rGKKhm
16+
CvQ=
17+
-----END CERTIFICATE REQUEST-----

test/resources/assets/dummy-list.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"builds": [
3+
{
4+
"path": "soljson-v0.8.16+commit.07a7930e.js",
5+
"version": "0.8.16",
6+
"build": "commit.07a7930e",
7+
"longVersion": "0.8.16+commit.07a7930e",
8+
"keccak256": "0x331f4bc6de3d44d87b68629e83f711105325b482da7e9ca9bdbdd01371fee438",
9+
"sha256": "0x27b2820ef93805a65c76b7945a49432582d306fd17a28985709a51e6403677c2",
10+
"urls": [
11+
"bzzr://af0d70945c85865298732ac2bfdacdf2774fb4daf793c94fafe135b839a60a5c",
12+
"dweb:/ipfs/QmWzBJ8gdccvRSSB5YsMKiF2qt3RFmAP2X25QEWqqQnR4y"
13+
]
14+
},
15+
{
16+
"path": "soljson-v0.8.17+commit.8df45f5f.js",
17+
"version": "0.8.17",
18+
"build": "commit.8df45f5f",
19+
"longVersion": "0.8.17+commit.8df45f5f",
20+
"keccak256": "0x3f2be218cf4545b4d2e380417c6da1e008fdc4255ab38c9ee12f64c0e3f55ea9",
21+
"sha256": "0x617828e63be485c7cc2dbcbdd5a22b582b40fafaa41016ad595637b83c90656c",
22+
"urls": [
23+
"bzzr://fe8da5b2531d31e4b67acdce09c81eccba1100550a7222722152ffdb16ea85ef",
24+
"dweb:/ipfs/QmTedx1wBKSUaLatuqXYngjfKQLD2cGqPKjdLYCnbMYwiz"
25+
]
26+
}
27+
],
28+
"releases": {
29+
"0.8.17": "soljson-v0.8.17+commit.8df45f5f.js",
30+
"0.8.16": "soljson-v0.8.16+commit.07a7930e.js"
31+
},
32+
"latestRelease": "0.8.17"
33+
}

0 commit comments

Comments
 (0)