Skip to content

Commit 20b6187

Browse files
authored
Merge pull request #160 from bcgov/feat/coms-unit-tests
Improve unit test coverage to above 50%
2 parents 6bd6b16 + 32dbf1e commit 20b6187

File tree

10 files changed

+1337
-76
lines changed

10 files changed

+1337
-76
lines changed

app/src/services/metadata.js

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -66,37 +66,6 @@ const service = {
6666
}
6767
},
6868

69-
/**
70-
* @function deleteOrphanedMetadata
71-
* deletes Metadata records if they are no longer related to any versions
72-
* @param {object} [etrx=undefined] An optional Objection Transaction object
73-
* @returns {Promise<number>} The result of running the delete operation (number of rows deleted)
74-
* @throws The error encountered upon db transaction failure
75-
*/
76-
// TODO: check if deleteing a version will prune orphan metadata records (sister table)
77-
pruneOrphanedMetadata: async (etrx = undefined) => {
78-
let trx;
79-
try {
80-
trx = etrx ? etrx : await Metadata.startTransaction();
81-
82-
const deletedMetadataIds = await Metadata.query(trx)
83-
.allowGraph('versionMetadata')
84-
.withGraphJoined('versionMetadata')
85-
.select('metadata.id')
86-
.whereNull('versionMetadata.metadataId');
87-
88-
const response = await Metadata.query(trx)
89-
.delete()
90-
.whereIn('id', deletedMetadataIds.map(({ id }) => id));
91-
92-
if (!etrx) await trx.commit();
93-
return Promise.resolve(response);
94-
} catch (err) {
95-
if (!etrx && trx) await trx.rollback();
96-
throw err;
97-
}
98-
},
99-
10069
/**
10170
* @function createMetadata
10271
* Inserts any metadata records if they dont already exist in db
@@ -232,6 +201,36 @@ const service = {
232201
}));
233202
},
234203

204+
/**
205+
* @function pruneOrphanedMetadata
206+
* deletes Metadata records if they are no longer related to any versions
207+
* @param {object} [etrx=undefined] An optional Objection Transaction object
208+
* @returns {Promise<number>} The result of running the delete operation (number of rows deleted)
209+
* @throws The error encountered upon db transaction failure
210+
*/
211+
// TODO: check if deleting a version will prune orphan metadata records (sister table)
212+
pruneOrphanedMetadata: async (etrx = undefined) => {
213+
let trx;
214+
try {
215+
trx = etrx ? etrx : await Metadata.startTransaction();
216+
217+
const deletedMetadataIds = await Metadata.query(trx)
218+
.allowGraph('versionMetadata')
219+
.withGraphJoined('versionMetadata')
220+
.select('metadata.id')
221+
.whereNull('versionMetadata.metadataId');
222+
223+
const response = await Metadata.query(trx)
224+
.delete()
225+
.whereIn('id', deletedMetadataIds.map(({ id }) => id));
226+
227+
if (!etrx) await trx.commit();
228+
return Promise.resolve(response);
229+
} catch (err) {
230+
if (!etrx && trx) await trx.rollback();
231+
throw err;
232+
}
233+
},
235234

236235
/**
237236
* @function searchMetadata

app/tests/common/helper.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,29 @@ const helper = {
4646
},
4747

4848
/**
49-
* @function resetReturnThis
50-
* Updates all jest mocked attributes in `obj` to `mockReturnThis`
51-
* @param {object} obj An object with some mocked attributes
49+
* @function resetModel
50+
* Resets a mock objection model
51+
* @param {object} obj A mock objection model
52+
* @param {object} trx A mock transaction object
5253
*/
53-
resetReturnThis: (obj) => {
54+
resetModel: (obj, trx) => {
55+
// Set all jest functions to return itself
5456
Object.keys(obj).forEach((f) => {
5557
if (jest.isMockFunction(obj[f])) obj[f].mockReturnThis();
5658
});
57-
}
59+
obj.startTransaction.mockImplementation(() => trx);
60+
obj.then.mockImplementation((resolve) => resolve(this));
61+
},
62+
63+
/**
64+
* @function trxBuilder
65+
* Returns a mock transaction object
66+
* @returns {object} A mock transaction object
67+
*/
68+
trxBuilder: () => ({
69+
commit: jest.fn(),
70+
rollback: jest.fn()
71+
})
5872
};
5973

6074
module.exports = helper;
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
const { NIL: BUCKET_ID, NIL: SYSTEM_USER } = require('uuid');
2+
3+
const { resetModel, trxBuilder } = require('../../common/helper');
4+
const Bucket = require('../../../src/db/models/tables/bucket');
5+
6+
const bucketTrx = trxBuilder();
7+
jest.mock('../../../src/db/models/tables/bucket', () => ({
8+
startTransaction: jest.fn(),
9+
then: jest.fn(),
10+
11+
allowGraph: jest.fn(),
12+
deleteById: jest.fn(),
13+
findById: jest.fn(),
14+
first: jest.fn(),
15+
insert: jest.fn(),
16+
modify: jest.fn(),
17+
patchAndFetchById: jest.fn(),
18+
query: jest.fn(),
19+
returning: jest.fn(),
20+
throwIfNotFound: jest.fn(),
21+
where: jest.fn()
22+
}));
23+
24+
const service = require('../../../src/services/bucket');
25+
const bucketPermissionService = require('../../../src/services/bucketPermission');
26+
27+
const data = {
28+
bucketId: BUCKET_ID,
29+
bucketName: 'bucketName',
30+
accessKeyId: 'accesskeyid',
31+
bucket: 'bucket',
32+
endpoint: 'endpoint',
33+
key: 'key',
34+
secretAccessKey: 'secretaccesskey',
35+
region: 'region',
36+
active: 'true',
37+
createdBy: SYSTEM_USER,
38+
userId: SYSTEM_USER
39+
};
40+
41+
beforeEach(() => {
42+
jest.clearAllMocks();
43+
resetModel(Bucket, bucketTrx);
44+
});
45+
46+
describe('checkGrantPermissions', () => {
47+
const readUniqueSpy = jest.spyOn(service, 'readUnique');
48+
49+
beforeEach(() => {
50+
readUniqueSpy.mockReset();
51+
});
52+
53+
afterAll(() => {
54+
readUniqueSpy.mockRestore();
55+
});
56+
57+
it('Grants a user full permissions to the bucket if the data precisely matches', async () => {
58+
readUniqueSpy.mockResolvedValue({ accessKeyId: data.accessKeyId, secretAccessKey: data.secretAccessKey });
59+
60+
await service.checkGrantPermissions(data);
61+
62+
expect(Bucket.startTransaction).toHaveBeenCalledTimes(1);
63+
expect(bucketTrx.commit).toHaveBeenCalledTimes(1);
64+
});
65+
});
66+
67+
describe('create', () => {
68+
const addPermissionsSpy = jest.spyOn(bucketPermissionService, 'addPermissions');
69+
70+
beforeEach(() => {
71+
addPermissionsSpy.mockReset();
72+
});
73+
74+
afterAll(() => {
75+
addPermissionsSpy.mockRestore();
76+
});
77+
78+
it('Create a bucket record and give the uploader (if authed) permissions', async () => {
79+
addPermissionsSpy.mockResolvedValue({ ...data });
80+
81+
await service.create(data);
82+
83+
expect(Bucket.startTransaction).toHaveBeenCalledTimes(1);
84+
expect(Bucket.query).toHaveBeenCalledTimes(1);
85+
expect(Bucket.insert).toHaveBeenCalledTimes(1);
86+
expect(Bucket.insert).toBeCalledWith(expect.anything());
87+
expect(Bucket.returning).toHaveBeenCalledTimes(1);
88+
expect(Bucket.returning).toBeCalledWith('*');
89+
expect(bucketTrx.commit).toHaveBeenCalledTimes(1);
90+
});
91+
});
92+
93+
describe('delete', () => {
94+
it('Delete a bucket record, this will also delete all objects and permissions', async () => {
95+
await service.delete(BUCKET_ID);
96+
97+
expect(Bucket.startTransaction).toHaveBeenCalledTimes(1);
98+
expect(Bucket.query).toHaveBeenCalledTimes(1);
99+
expect(Bucket.deleteById).toHaveBeenCalledTimes(1);
100+
expect(Bucket.deleteById).toBeCalledWith(BUCKET_ID);
101+
expect(Bucket.throwIfNotFound).toHaveBeenCalledTimes(1);
102+
expect(Bucket.throwIfNotFound).toBeCalledWith();
103+
expect(Bucket.returning).toHaveBeenCalledTimes(1);
104+
expect(Bucket.returning).toBeCalledWith('*');
105+
expect(bucketTrx.commit).toHaveBeenCalledTimes(1);
106+
});
107+
});
108+
109+
describe('searchBuckets', () => {
110+
it('search and filter for specific bucket records', async () => {
111+
Bucket.then.mockImplementation(() => { });
112+
113+
await service.searchBuckets([]);
114+
115+
expect(Bucket.query).toHaveBeenCalledTimes(1);
116+
expect(Bucket.allowGraph).toHaveBeenCalledTimes(1);
117+
expect(Bucket.modify).toHaveBeenCalledTimes(5);
118+
expect(Bucket.then).toHaveBeenCalledTimes(1);
119+
});
120+
});
121+
122+
describe('read', () => {
123+
it('Get a bucket db record based on bucketId', async () => {
124+
await service.read(BUCKET_ID);
125+
126+
expect(Bucket.query).toHaveBeenCalledTimes(1);
127+
expect(Bucket.findById).toHaveBeenCalledTimes(1);
128+
expect(Bucket.findById).toBeCalledWith(BUCKET_ID);
129+
expect(Bucket.throwIfNotFound).toHaveBeenCalledTimes(1);
130+
expect(Bucket.throwIfNotFound).toBeCalledWith();
131+
});
132+
});
133+
134+
describe('readUnique', () => {
135+
it('Get a bucket db record based on unique parameters', async () => {
136+
await service.readUnique(data);
137+
138+
expect(Bucket.query).toHaveBeenCalledTimes(1);
139+
expect(Bucket.where).toHaveBeenCalledTimes(3);
140+
expect(Bucket.where).toBeCalledWith('bucket', expect.any(String));
141+
expect(Bucket.where).toBeCalledWith('endpoint', expect.any(String));
142+
expect(Bucket.where).toBeCalledWith('key', expect.any(String));
143+
expect(Bucket.first).toHaveBeenCalledTimes(1);
144+
expect(Bucket.first).toBeCalledWith();
145+
expect(Bucket.throwIfNotFound).toHaveBeenCalledTimes(1);
146+
expect(Bucket.throwIfNotFound).toBeCalledWith();
147+
});
148+
});
149+
150+
describe('update', () => {
151+
it('Update a bucket DB record', async () => {
152+
await service.update(data);
153+
154+
expect(Bucket.startTransaction).toHaveBeenCalledTimes(1);
155+
expect(Bucket.query).toHaveBeenCalledTimes(1);
156+
expect(Bucket.patchAndFetchById).toHaveBeenCalledTimes(1);
157+
expect(Bucket.patchAndFetchById).toBeCalledWith(data.bucketId, {
158+
bucketName: data.bucketName,
159+
accessKeyId: data.accessKeyId,
160+
bucket: data.bucket,
161+
endpoint: data.endpoint,
162+
key: data.key,
163+
secretAccessKey: data.secretAccessKey,
164+
region: data.region,
165+
active: data.active,
166+
updatedBy: data.userId
167+
});
168+
expect(bucketTrx.commit).toHaveBeenCalledTimes(1);
169+
});
170+
});
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
const { NIL: BUCKET_ID, NIL: OBJECT_ID, NIL: SYSTEM_USER } = require('uuid');
2+
3+
const { resetModel, trxBuilder } = require('../../common/helper');
4+
const BucketPermission = require('../../../src/db/models/tables/bucketPermission');
5+
const ObjectPermission = require('../../../src/db/models/tables/objectPermission');
6+
7+
const bucketPermissionTrx = trxBuilder();
8+
jest.mock('../../../src/db/models/tables/bucketPermission', () => ({
9+
startTransaction: jest.fn(),
10+
then: jest.fn(),
11+
12+
delete: jest.fn(),
13+
insertAndFetch: jest.fn(),
14+
modify: jest.fn(),
15+
query: jest.fn(),
16+
returning: jest.fn()
17+
}));
18+
19+
const objectPermissionTrx = trxBuilder();
20+
jest.mock('../../../src/db/models/tables/objectPermission', () => ({
21+
startTransaction: jest.fn(),
22+
then: jest.fn(),
23+
24+
distinct: jest.fn(),
25+
joinRelated: jest.fn(),
26+
modify: jest.fn(),
27+
query: jest.fn(),
28+
select: jest.fn(),
29+
whereNotNull: jest.fn()
30+
}));
31+
32+
const service = require('../../../src/services/bucketPermission');
33+
34+
const data = [{
35+
id: OBJECT_ID,
36+
bucketId: BUCKET_ID,
37+
path: 'path',
38+
public: 'true',
39+
active: 'true',
40+
createdBy: SYSTEM_USER,
41+
permCode: 'READ'
42+
}];
43+
44+
beforeEach(() => {
45+
jest.clearAllMocks();
46+
resetModel(BucketPermission, bucketPermissionTrx);
47+
resetModel(ObjectPermission, objectPermissionTrx);
48+
});
49+
50+
describe('addPermissions', () => {
51+
const searchPermissionsSpy = jest.spyOn(service, 'searchPermissions');
52+
53+
beforeEach(() => {
54+
searchPermissionsSpy.mockReset();
55+
});
56+
57+
afterAll(() => {
58+
searchPermissionsSpy.mockRestore();
59+
});
60+
61+
it('Grants bucket permissions to users', async () => {
62+
searchPermissionsSpy.mockResolvedValue([{ userId: SYSTEM_USER, permCode: 'READ' }]);
63+
64+
await service.addPermissions(BUCKET_ID, data);
65+
66+
expect(BucketPermission.startTransaction).toHaveBeenCalledTimes(1);
67+
expect(BucketPermission.insertAndFetch).toHaveBeenCalledTimes(1);
68+
expect(BucketPermission.insertAndFetch).toBeCalledWith(expect.anything());
69+
expect(bucketPermissionTrx.commit).toHaveBeenCalledTimes(1);
70+
});
71+
});
72+
73+
describe('removePermissions', () => {
74+
it('Deletes bucket permissions for a user', async () => {
75+
await service.removePermissions(BUCKET_ID, [SYSTEM_USER]);
76+
77+
expect(BucketPermission.startTransaction).toHaveBeenCalledTimes(1);
78+
expect(BucketPermission.delete).toHaveBeenCalledTimes(1);
79+
expect(BucketPermission.delete).toBeCalledWith();
80+
expect(BucketPermission.modify).toHaveBeenCalledTimes(3);
81+
expect(BucketPermission.modify).toBeCalledWith('filterUserId', [SYSTEM_USER]);
82+
expect(BucketPermission.modify).toBeCalledWith('filterBucketId', BUCKET_ID);
83+
expect(BucketPermission.returning).toHaveBeenCalledTimes(1);
84+
expect(BucketPermission.returning).toBeCalledWith('*');
85+
expect(bucketPermissionTrx.commit).toHaveBeenCalledTimes(1);
86+
});
87+
});
88+
89+
describe('getBucketIdsWithObject', () => {
90+
it('Searches for specific (bucket) object permissions', async () => {
91+
ObjectPermission.then.mockImplementation();
92+
93+
await service.getBucketIdsWithObject();
94+
95+
expect(ObjectPermission.query).toHaveBeenCalledTimes(1);
96+
expect(ObjectPermission.select).toHaveBeenCalledTimes(1);
97+
expect(ObjectPermission.distinct).toHaveBeenCalledTimes(1);
98+
expect(ObjectPermission.joinRelated).toHaveBeenCalledTimes(1);
99+
expect(ObjectPermission.modify).toHaveBeenCalledTimes(1);
100+
expect(ObjectPermission.whereNotNull).toHaveBeenCalledTimes(1);
101+
expect(ObjectPermission.then).toHaveBeenCalledTimes(1);
102+
});
103+
});
104+
105+
describe('searchPermissions', () => {
106+
it('Search and filter for specific bucket permissions', () => {
107+
service.searchPermissions({ userId: SYSTEM_USER, bucketId: BUCKET_ID, permCode: 'READ' });
108+
109+
expect(BucketPermission.query).toHaveBeenCalledTimes(1);
110+
expect(BucketPermission.modify).toHaveBeenCalledTimes(3);
111+
expect(BucketPermission.modify).toBeCalledWith('filterUserId', SYSTEM_USER);
112+
expect(BucketPermission.modify).toBeCalledWith('filterBucketId', BUCKET_ID);
113+
expect(BucketPermission.modify).toBeCalledWith('filterPermissionCode', 'READ');
114+
expect(BucketPermission.modify).toHaveBeenCalledTimes(3);
115+
});
116+
});

0 commit comments

Comments
 (0)