Skip to content

Commit 19dccdd

Browse files
committed
Adds functioning tests
1 parent 884fc9f commit 19dccdd

File tree

5 files changed

+241
-0
lines changed

5 files changed

+241
-0
lines changed

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
branches:
2+
only:
3+
- master
4+
language: node_js
5+
node_js:
6+
- "4.3"

index.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
'use strict';
2+
// S3Adapter
3+
//
4+
// Stores Parse files in AWS S3.
5+
6+
var AWS = require('aws-sdk');
7+
const DEFAULT_S3_REGION = "us-east-1";
8+
9+
function requiredOrFromEnvironment(options, key, env) {
10+
options[key] = options[key] || process.env[env];
11+
if (!options[key]) {
12+
throw `S3Adapter requires option '${key}' or env. variable ${env}`;
13+
}
14+
return options;
15+
}
16+
17+
function fromEnvironmentOrDefault(options, key, env, defaultValue) {
18+
options[key] = options[key] || process.env[env] || defaultValue;
19+
return options;
20+
}
21+
22+
function optionsFromArguments(args) {
23+
let options = {};
24+
let accessKeyOrOptions = args[0];
25+
if (typeof accessKeyOrOptions == 'string') {
26+
options.accessKey = accessKeyOrOptions;
27+
options.secretKey = args[1];
28+
options.bucket = args[2];
29+
let otherOptions = args[3];
30+
if (otherOptions) {
31+
options.bucketPrefix = otherOptions.bucketPrefix;
32+
options.directAccess = otherOptions.directAccess;
33+
}
34+
} else {
35+
options = accessKeyOrOptions || {};
36+
}
37+
options = requiredOrFromEnvironment(options, 'accessKey', 'S3_ACCESS_KEY');
38+
options = requiredOrFromEnvironment(options, 'secretKey', 'S3_SECRET_KEY');
39+
options = requiredOrFromEnvironment(options, 'bucket', 'S3_BUCKET');
40+
options = fromEnvironmentOrDefault(options, 'bucketPrefix', 'S3_BUCKET_PREFIX', '');
41+
options = fromEnvironmentOrDefault(options, 'region', 'S3_REGION', false);
42+
options = fromEnvironmentOrDefault(options, 'directAccess', 'S3_DIRECT_ACCESS', false);
43+
return options;
44+
}
45+
46+
// Creates an S3 session.
47+
// Providing AWS access, secret keys and bucket are mandatory
48+
// Region will use sane defaults if omitted
49+
function S3Adapter() {
50+
var options = optionsFromArguments(arguments);
51+
this._region = options.region;
52+
this._bucket = options.bucket;
53+
this._bucketPrefix = options.bucketPrefix;
54+
this._directAccess = options.directAccess;
55+
56+
let s3Options = {
57+
accessKeyId: options.accessKey,
58+
secretAccessKey: options.secretKey,
59+
params: { Bucket: this._bucket }
60+
};
61+
AWS.config._region = this._region;
62+
this._s3Client = new AWS.S3(s3Options);
63+
this._hasBucket = false;
64+
}
65+
66+
S3Adapter.prototype.createBucket = function() {
67+
var promise;
68+
if (this._hasBucket) {
69+
promise = Promise.resolve();
70+
} else {
71+
promise = new Promise((resolve, reject) => {
72+
this._s3Client.createBucket(() => {
73+
this._hasBucket = true;
74+
resolve();
75+
});
76+
});
77+
}
78+
return promise;
79+
}
80+
81+
// For a given config object, filename, and data, store a file in S3
82+
// Returns a promise containing the S3 object creation response
83+
S3Adapter.prototype.createFile = function(filename, data, contentType) {
84+
let params = {
85+
Key: this._bucketPrefix + filename,
86+
Body: data
87+
};
88+
if (this._directAccess) {
89+
params.ACL = "public-read"
90+
}
91+
if (contentType) {
92+
params.ContentType = contentType;
93+
}
94+
return this.createBucket().then(() => {
95+
return new Promise((resolve, reject) => {
96+
this._s3Client.upload(params, (err, data) => {
97+
if (err !== null) {
98+
return reject(err);
99+
}
100+
resolve(data);
101+
});
102+
});
103+
});
104+
}
105+
106+
S3Adapter.prototype.deleteFile = function(filename) {
107+
return this.createBucket().then(() => {
108+
return new Promise((resolve, reject) => {
109+
let params = {
110+
Key: this._bucketPrefix + filename
111+
};
112+
this._s3Client.deleteObject(params, (err, data) =>{
113+
if(err !== null) {
114+
return reject(err);
115+
}
116+
resolve(data);
117+
});
118+
});
119+
});
120+
}
121+
122+
// Search for and return a file if found by filename
123+
// Returns a promise that succeeds with the buffer result from S3
124+
S3Adapter.prototype.getFileData = function(filename) {
125+
let params = {Key: this._bucketPrefix + filename};
126+
return this.createBucket().then(() => {
127+
return new Promise((resolve, reject) => {
128+
this._s3Client.getObject(params, (err, data) => {
129+
if (err !== null) {
130+
return reject(err);
131+
}
132+
// Something happend here...
133+
if (data && !data.Body) {
134+
return reject(data);
135+
}
136+
resolve(data.Body);
137+
});
138+
});
139+
});
140+
}
141+
142+
// Generates and returns the location of a file stored in S3 for the given request and filename
143+
// The location is the direct S3 link if the option is set, otherwise we serve the file through parse-server
144+
S3Adapter.prototype.getFileLocation = function(config, filename) {
145+
if (this._directAccess) {
146+
return `https://${this._bucket}.s3.amazonaws.com/${this._bucketPrefix + filename}`;
147+
}
148+
return (config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename));
149+
}
150+
151+
module.exports = S3Adapter;
152+
module.exports.default = S3Adapter;

package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "parse-server-s3-adapter",
3+
"version": "1.0.0",
4+
"description": "AWS S3 adapter for parse-server",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "jasmine"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/parse-server-modules/parse-server-s3-adapter.git"
12+
},
13+
"keywords": [
14+
"parse-server",
15+
"AWS",
16+
"S3"
17+
],
18+
"author": "Parse",
19+
"license": "ISC",
20+
"bugs": {
21+
"url": "https://github.com/parse-server-modules/parse-server-s3-adapter/issues"
22+
},
23+
"homepage": "https://github.com/parse-server-modules/parse-server-s3-adapter#readme",
24+
"dependencies": {
25+
"aws-sdk": "^2.2.46",
26+
"jasmine": "^2.4.1",
27+
"parse-server-conformance-tests": "^1.0.0"
28+
}
29+
}

spec/support/jasmine.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"spec_dir": "spec",
3+
"spec_files": [
4+
"test.spec.js"
5+
]
6+
}

spec/test.spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
let filesAdapterTests = require('parse-server-conformance-tests').files;
3+
4+
let S3Adapter = require('../index.js');
5+
6+
describe('S3Adapter tests', () => {
7+
8+
it('should throw when not initialized properly', () => {
9+
expect(() => {
10+
var s3 = new S3Adapter();
11+
}).toThrow("S3Adapter requires option 'accessKey' or env. variable S3_ACCESS_KEY")
12+
13+
expect(() => {
14+
var s3 = new S3Adapter('accessKey');
15+
}).toThrow("S3Adapter requires option 'secretKey' or env. variable S3_SECRET_KEY")
16+
17+
expect(() => {
18+
var s3 = new S3Adapter('accessKey', 'secretKey');
19+
}).toThrow("S3Adapter requires option 'bucket' or env. variable S3_BUCKET")
20+
21+
expect(() => {
22+
var s3 = new S3Adapter({ accessKey: 'accessKey'});
23+
}).toThrow("S3Adapter requires option 'secretKey' or env. variable S3_SECRET_KEY")
24+
expect(() => {
25+
var s3 = new S3Adapter({ accessKey: 'accessKey' , secretKey: 'secretKey'});
26+
}).toThrow("S3Adapter requires option 'bucket' or env. variable S3_BUCKET")
27+
})
28+
29+
it('should not throw when initialized properly', () => {
30+
expect(() => {
31+
var s3 = new S3Adapter('accessKey', 'secretKey', 'bucket');
32+
}).not.toThrow()
33+
34+
expect(() => {
35+
var s3 = new S3Adapter({ accessKey: 'accessKey' , secretKey: 'secretKey', bucket: 'bucket'});
36+
}).not.toThrow()
37+
})
38+
39+
if (process.env.TEST_S3_ACCESS_KEY && process.env.TEST_S3_SECRET_KEY && process.env.TEST_S3_BUCKET) {
40+
// Should be initialized from the env
41+
let s3 = new S3Adapter({
42+
accessKey: process.env.TEST_S3_ACCESS_KEY,
43+
secretKey: process.env.TEST_S3_SECRET_KEY,
44+
bucket: process.env.TEST_S3_BUCKET
45+
});
46+
filesAdapterTests.testAdapter("S3Adapter", s3);
47+
}
48+
})

0 commit comments

Comments
 (0)