Skip to content

Commit 5a8d187

Browse files
committed
add tests
Issue: CLDSRV-728
1 parent b3239d2 commit 5a8d187

File tree

4 files changed

+331
-5
lines changed

4 files changed

+331
-5
lines changed

lib/routes/routeBackbeat.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1701,4 +1701,7 @@ function routeBackbeat(clientIP, request, response, log, callback) {
17011701
}
17021702

17031703

1704-
module.exports = routeBackbeat;
1704+
module.exports = {
1705+
backbeatRoutes,
1706+
routeBackbeat,
1707+
};

lib/utilities/internalHandlers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const routeBackbeat = require('../routes/routeBackbeat');
1+
const { routeBackbeat } = require('../routes/routeBackbeat');
22
const routeMetadata = require('../routes/routeMetadata');
33
const routeWorkflowEngineOperator =
44
require('../routes/routeWorkflowEngineOperator');

tests/unit/DummyRequest.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ class DummyRequest extends http.IncomingMessage {
2525
this.push(msg);
2626
}
2727
this.push(null);
28+
if (!this.socket) {
29+
this.socket = {
30+
remoteAddress: '127.0.0.1',
31+
destroy: () => {},
32+
on: () => {},
33+
removeListener: () => {},
34+
};
35+
}
2836
}
2937

3038
_destroy(err, cb) {

tests/unit/routes/routeBackbeat.js

Lines changed: 318 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
const assert = require('assert');
22
const sinon = require('sinon');
3+
const async = require('async');
34
const { promisify } = require('util');
45
const metadataUtils = require('../../../lib/metadata/metadataUtils');
56
const storeObject = require('../../../lib/api/apiUtils/object/storeObject');
67
const metadata = require('../../../lib/metadata/wrapper');
7-
const { DummyRequestLogger } = require('../helpers');
8+
const { routeBackbeat, backbeatRoutes } = require('../../../lib/routes/routeBackbeat');
9+
const { DummyRequestLogger, versioningTestUtils, makeAuthInfo } = require('../helpers');
810
const dataWrapper = require('../../../lib/data/wrapper');
911
const DummyRequest = require('../DummyRequest');
10-
const { auth } = require('arsenal');
12+
const { auth, errors } = require('arsenal');
13+
const { default: AuthInfo } = require('arsenal/build/lib/auth/AuthInfo');
1114
const { config } = require('../../../lib/Config');
1215
const quotaUtils = require('../../../lib/api/apiUtils/quotas/quotaUtils');
16+
const { bucketPut } = require('../../../lib/api/bucketPut');
17+
const objectPut = require('../../../lib/api/objectPut');
18+
const bucketPutVersioning = require('../../../lib/api/bucketPutVersioning');
1319

1420
const log = new DummyRequestLogger();
1521

@@ -67,7 +73,7 @@ describe('routeBackbeat', () => {
6773

6874
// Clear require cache for routeBackbeat to make sure fresh module with stubbed dependencies
6975
delete require.cache[require.resolve('../../../lib/routes/routeBackbeat')];
70-
routeBackbeat = require('../../../lib/routes/routeBackbeat');
76+
routeBackbeat = require('../../../lib/routes/routeBackbeat').routeBackbeat;
7177
});
7278

7379
afterEach(() => {
@@ -694,3 +700,312 @@ describe('routeBackbeat', () => {
694700
});
695701
});
696702
});
703+
704+
describe('routeBackbeat authorization', () => {
705+
const bucketName = 'bucketname';
706+
const canonicalID = 'accessKey1';
707+
const authInfo = makeAuthInfo(canonicalID);
708+
const namespace = 'default';
709+
const objectName = 'objectName';
710+
const postBody = Buffer.from('I am a body', 'utf8');
711+
712+
const testBucket = {
713+
bucketName,
714+
namespace,
715+
headers: {
716+
'host': `${bucketName}.s3.amazonaws.com`,
717+
},
718+
url: `/${bucketName}`,
719+
actionImplicitDenies: false,
720+
};
721+
722+
const testObject = new DummyRequest({
723+
bucketName,
724+
namespace,
725+
objectKey: objectName,
726+
headers: {
727+
'x-amz-meta-test': 'some metadata',
728+
'content-length': '12',
729+
},
730+
parsedContentLength: 12,
731+
url: `/${bucketName}/${objectName}`,
732+
}, postBody);
733+
734+
let request;
735+
let response;
736+
737+
beforeEach(() => {
738+
sinon.stub(backbeatRoutes, 'PUT').returns({
739+
data: sinon.stub(),
740+
metadata: sinon.stub(),
741+
multiplebackenddata: {
742+
putobject: sinon.stub(),
743+
putpart: sinon.stub(),
744+
},
745+
});
746+
747+
sinon.stub(backbeatRoutes, 'POST').returns({
748+
multiplebackenddata: {
749+
initiatempu: sinon.stub(),
750+
completempu: sinon.stub(),
751+
puttagging: sinon.stub(),
752+
},
753+
batchdelete: sinon.stub(),
754+
index: {
755+
add: sinon.stub(),
756+
delete: sinon.stub(),
757+
},
758+
});
759+
760+
sinon.stub(backbeatRoutes, 'DELETE').returns({
761+
expiration: sinon.stub(),
762+
multiplebackenddata: {
763+
deleteobject: sinon.stub(),
764+
deleteobjecttagging: sinon.stub(),
765+
abortmpu: sinon.stub(),
766+
},
767+
});
768+
769+
sinon.stub(backbeatRoutes, 'GET').returns({
770+
metadata: sinon.stub(),
771+
multiplebackendmetadata: sinon.stub(),
772+
lifecycle: sinon.stub(),
773+
index: sinon.stub(),
774+
});
775+
776+
request = new DummyRequest(
777+
{
778+
method: 'GET',
779+
headers: { 'content-length': '123' },
780+
url: '/_/backbeat/multiplebackendmetadata/bucketName/objectKey?operation=putobject',
781+
},
782+
'body'
783+
);
784+
response = {
785+
setHeader: sinon.stub(),
786+
writeHead: sinon.stub(),
787+
end: sinon.stub().callsFake((body, format, cb) => cb()),
788+
};
789+
});
790+
791+
afterEach(() => {
792+
sinon.restore();
793+
});
794+
795+
it('should reject if the request is invalid', done => {
796+
request.url = '/_/backbeat//bucketName/objectKey?operation=putobject';
797+
// Cover the case invalidRequest === true
798+
routeBackbeat('127.0.0.1', request, response, log, err => {
799+
assert(err.is.MethodNotAllowed);
800+
done();
801+
});
802+
});
803+
804+
it('should reject if the route is invalid', done => {
805+
request.url = '/_/backbeat/wrong/bucketName/objectKey?operation=putobject';
806+
// Cover the case invalidRoute === true
807+
routeBackbeat('127.0.0.1', request, response, log, err => {
808+
assert(err.is.MethodNotAllowed);
809+
done();
810+
});
811+
});
812+
813+
[
814+
{
815+
method: 'PUT',
816+
resourceType: 'metadata',
817+
target: `${bucketName}/${objectName}`,
818+
operation: null,
819+
versionId: false,
820+
expect: errors.MalformedPOSTRequest,
821+
},
822+
{
823+
method: 'GET',
824+
resourceType: 'metadata',
825+
target: `${bucketName}/${objectName}`,
826+
operation: null,
827+
versionId: true,
828+
},
829+
{
830+
method: 'PUT',
831+
resourceType: 'data',
832+
target: `${bucketName}/${objectName}`,
833+
operation: null,
834+
versionId: false,
835+
expect: errors.BadRequest,
836+
},
837+
{
838+
method: 'PUT',
839+
resourceType: 'multiplebackenddata',
840+
target: `${bucketName}/${objectName}`,
841+
operation: 'putobject',
842+
versionId: false,
843+
expect: errors.BadRequest,
844+
},
845+
{
846+
method: 'PUT',
847+
resourceType: 'multiplebackenddata',
848+
target: `${bucketName}/${objectName}`,
849+
operation: 'putpart',
850+
versionId: false,
851+
expect: errors.BadRequest,
852+
},
853+
{
854+
method: 'DELETE',
855+
resourceType: 'multiplebackenddata',
856+
target: `${bucketName}/${objectName}`,
857+
operation: 'deleteobject',
858+
versionId: false,
859+
expect: errors.BadRequest,
860+
},
861+
{
862+
method: 'DELETE',
863+
resourceType: 'multiplebackenddata',
864+
target: `${bucketName}/${objectName}`,
865+
operation: 'abortmpu',
866+
versionId: false,
867+
expect: errors.BadRequest,
868+
},
869+
{
870+
method: 'DELETE',
871+
resourceType: 'multiplebackenddata',
872+
target: `${bucketName}/${objectName}`,
873+
operation: 'deleteobjecttagging',
874+
versionId: false,
875+
expect: errors.BadRequest,
876+
},
877+
{
878+
method: 'POST',
879+
resourceType: 'multiplebackenddata',
880+
target: `${bucketName}/${objectName}`,
881+
operation: 'initiatempu',
882+
versionId: false,
883+
expect: errors.BadRequest,
884+
},
885+
{
886+
method: 'POST',
887+
resourceType: 'multiplebackenddata',
888+
target: `${bucketName}/${objectName}`,
889+
operation: 'completempu',
890+
versionId: false,
891+
expect: errors.BadRequest,
892+
},
893+
{
894+
method: 'POST',
895+
resourceType: 'multiplebackenddata',
896+
target: `${bucketName}/${objectName}`,
897+
operation: 'puttagging',
898+
versionId: false,
899+
expect: errors.BadRequest,
900+
},
901+
{
902+
method: 'GET',
903+
resourceType: 'multiplebackendmetadata',
904+
target: `${bucketName}/${objectName}`,
905+
operation: null,
906+
versionId: false,
907+
expect: errors.BadRequest,
908+
},
909+
{
910+
method: 'POST',
911+
resourceType: 'batchdelete',
912+
target: null,
913+
operation: null,
914+
versionId: false,
915+
expect: errors.MalformedPOSTRequest,
916+
},
917+
{
918+
method: 'GET',
919+
resourceType: 'lifecycle',
920+
target: `${bucketName}?list-type=wrong`,
921+
operation: null,
922+
versionId: false,
923+
expect: errors.BadRequest,
924+
},
925+
{
926+
method: 'POST',
927+
resourceType: 'index',
928+
target: bucketName,
929+
operation: 'add',
930+
versionId: false,
931+
expect: errors.BadRequest,
932+
},
933+
{
934+
method: 'POST',
935+
resourceType: 'index',
936+
target: bucketName,
937+
operation: 'delete',
938+
versionId: false,
939+
expect: errors.BadRequest,
940+
},
941+
{
942+
method: 'GET',
943+
resourceType: 'index',
944+
target: null,
945+
operation: 'delete',
946+
versionId: false,
947+
expect: errors.NotImplemented,
948+
}
949+
].forEach(testCase => {
950+
it(`should call method ${testCase.method} ${testCase.resourceType}`, done => {
951+
let hasQuery = false;
952+
let versionIdParsed = null;
953+
request.method = testCase.method;
954+
request.url = `/_/backbeat/${testCase.resourceType}/${testCase.target}`;
955+
if (testCase.operation) {
956+
request.url += `?operation=${testCase.operation}`;
957+
hasQuery = true;
958+
}
959+
960+
// Mock auth server to ignore auth in this test
961+
sinon.stub(auth.server, 'doAuth').callsFake((req, log, cb) =>
962+
cb(null, new AuthInfo({
963+
canonicalID: 'abcdef/lifecycle',
964+
accountDisplayName: 'Lifecycle Service Account',
965+
}), undefined, undefined, {
966+
accountQuota: 1000,
967+
})
968+
);
969+
970+
const enableVersioningRequest =
971+
versioningTestUtils.createBucketPutVersioningReq(bucketName, 'Enabled');
972+
973+
return async.series([
974+
next => bucketPut(authInfo, testBucket, log, next),
975+
next => bucketPutVersioning(authInfo, enableVersioningRequest, log, next),
976+
next => objectPut(authInfo, testObject, undefined, log, (err, res) => {
977+
versionIdParsed = res['x-amz-version-id'];
978+
if (testCase.versionId) {
979+
request.url += `${(hasQuery ? '&' : '?')}&versionId=${versionIdParsed}`;
980+
}
981+
next(err);
982+
}),
983+
next => routeBackbeat('127.0.0.1', request, response, log, next),
984+
], err => {
985+
if (testCase.expect) {
986+
assert.strictEqual(err.code, testCase.expect.code);
987+
return done();
988+
}
989+
assert.ifError(err);
990+
assert.strictEqual(Array.isArray(request.finalizerHooks), true);
991+
assert.strictEqual(request.apiMethods[0], 'objectReplicate');
992+
assert.strictEqual(request.apiMethods.length, 1);
993+
assert.strictEqual(request.accountQuotas, 1000);
994+
return done();
995+
});
996+
});
997+
});
998+
999+
// Although the authz result is by default an implicit deny, the
1000+
// ACL should prevent any further processing for non-service or
1001+
// non-account identities.
1002+
it('should return access denied if doAuth returns an error', done => {
1003+
request.method = 'PUT';
1004+
request.url = `/_/backbeat/metadata/${bucketName}/${objectName}?operation=putobject`;
1005+
1006+
routeBackbeat('127.0.0.1', request, response, log, err => {
1007+
assert(err.is.AccessDenied);
1008+
done();
1009+
});
1010+
});
1011+
});

0 commit comments

Comments
 (0)