From 4e7579dc107a7fc39b59debd8403cd0fc54db9d6 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 16 Jun 2022 17:58:56 +0500 Subject: [PATCH] chore(specs): specs added for deploy upload file and zipped file --- api/package-lock.json | 35 ++++ api/package.json | 2 + api/src/routes/api/spec/drive.spec.ts | 286 +++++++++++++++++++++++++- api/src/utils/zipped.ts | 1 + 4 files changed, 319 insertions(+), 5 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 5c9ba17..6be30ee 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -32,6 +32,7 @@ "api": "build/src/server.js" }, "devDependencies": { + "@types/adm-zip": "^0.5.0", "@types/bcryptjs": "^2.4.2", "@types/cookie-parser": "^1.4.2", "@types/cors": "^2.8.12", @@ -47,6 +48,7 @@ "@types/supertest": "^2.0.11", "@types/swagger-ui-express": "^4.1.3", "@types/unzipper": "^0.10.5", + "adm-zip": "^0.5.9", "dotenv": "^10.0.0", "http-headers-validation": "^0.0.1", "jest": "^27.0.6", @@ -1755,6 +1757,15 @@ "yarn": ">=1.9.4" } }, + "node_modules/@types/adm-zip": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.0.tgz", + "integrity": "sha512-FCJBJq9ODsQZUNURo5ILAQueuA8WJhRvuihS3ke2iI25mJlfV2LK8jG2Qj2z2AWg8U0FtWWqBHVRetceLskSaw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/babel__core": { "version": "7.1.15", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.15.tgz", @@ -2283,6 +2294,15 @@ "node": ">=0.4.0" } }, + "node_modules/adm-zip": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", + "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -11886,6 +11906,15 @@ "validator": "^13.6.0" } }, + "@types/adm-zip": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.0.tgz", + "integrity": "sha512-FCJBJq9ODsQZUNURo5ILAQueuA8WJhRvuihS3ke2iI25mJlfV2LK8jG2Qj2z2AWg8U0FtWWqBHVRetceLskSaw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/babel__core": { "version": "7.1.15", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.15.tgz", @@ -12372,6 +12401,12 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, + "adm-zip": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", + "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "dev": true + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", diff --git a/api/package.json b/api/package.json index b2b0d12..bbd4bbb 100644 --- a/api/package.json +++ b/api/package.json @@ -68,6 +68,7 @@ "url": "^0.10.3" }, "devDependencies": { + "@types/adm-zip": "^0.5.0", "@types/bcryptjs": "^2.4.2", "@types/cookie-parser": "^1.4.2", "@types/cors": "^2.8.12", @@ -83,6 +84,7 @@ "@types/supertest": "^2.0.11", "@types/swagger-ui-express": "^4.1.3", "@types/unzipper": "^0.10.5", + "adm-zip": "^0.5.9", "dotenv": "^10.0.0", "http-headers-validation": "^0.0.1", "jest": "^27.0.6", diff --git a/api/src/routes/api/spec/drive.spec.ts b/api/src/routes/api/spec/drive.spec.ts index 341bd02..e9e6e8a 100644 --- a/api/src/routes/api/spec/drive.spec.ts +++ b/api/src/routes/api/spec/drive.spec.ts @@ -3,6 +3,7 @@ import { Express } from 'express' import mongoose, { Mongoose } from 'mongoose' import { MongoMemoryServer } from 'mongodb-memory-server' import request from 'supertest' +import AdmZip from 'adm-zip' import { folderExists, @@ -72,11 +73,52 @@ describe('drive', () => { }) describe('deploy', () => { - const shouldFailAssertion = async (payload: any) => { - const res = await request(app) - .post('/SASjsApi/drive/deploy') - .auth(accessToken, { type: 'bearer' }) - .send({ appLoc: '/Public', fileTree: payload }) + const makeRequest = async (payload: any, type: string = 'payload') => { + const requestUrl = + type === 'payload' + ? '/SASjsApi/drive/deploy' + : '/SASjsApi/drive/deploy/upload' + + if (type === 'payload') { + return await request(app) + .post(requestUrl) + .auth(accessToken, { type: 'bearer' }) + .send({ appLoc: '/Public', fileTree: payload }) + } + if (type === 'file') { + const deployContents = JSON.stringify({ + appLoc: '/Public', + fileTree: payload + }) + return await request(app) + .post(requestUrl) + .auth(accessToken, { type: 'bearer' }) + .attach('file', Buffer.from(deployContents), 'deploy.json') + } else { + const deployContents = JSON.stringify({ + appLoc: '/Public', + fileTree: payload + }) + const zip = new AdmZip() + // add file directly + zip.addFile( + 'deploy.json', + Buffer.from(deployContents, 'utf8'), + 'entry comment goes here' + ) + + return await request(app) + .post(requestUrl) + .auth(accessToken, { type: 'bearer' }) + .attach('file', zip.toBuffer(), 'deploy.json.zip') + } + } + + const shouldFailAssertion = async ( + payload: any, + type: string = 'payload' + ) => { + const res = await makeRequest(payload, type) expect(res.statusCode).toEqual(400) @@ -176,6 +218,240 @@ describe('drive', () => { await deleteFolder(path.join(getFilesFolder(), 'public')) }) + + describe('upload', () => { + it('should respond with payload example if valid JSON file was not provided', async () => { + await shouldFailAssertion(null, 'file') + await shouldFailAssertion(undefined, 'file') + await shouldFailAssertion('data', 'file') + await shouldFailAssertion({}, 'file') + await shouldFailAssertion( + { + userId: 1, + title: 'test is cool' + }, + 'file' + ) + await shouldFailAssertion( + { + membersWRONG: [] + }, + 'file' + ) + await shouldFailAssertion( + { + members: {} + }, + 'file' + ) + await shouldFailAssertion( + { + members: [ + { + nameWRONG: 'jobs', + type: 'folder', + members: [] + } + ] + }, + 'file' + ) + await shouldFailAssertion( + { + members: [ + { + name: 'jobs', + type: 'WRONG', + members: [] + } + ] + }, + 'file' + ) + await shouldFailAssertion( + { + members: [ + { + name: 'jobs', + type: 'folder', + members: [ + { + name: 'extract', + type: 'folder', + members: [ + { + name: 'makedata1', + type: 'service', + codeWRONG: '%put Hello World!;' + } + ] + } + ] + } + ] + }, + 'file' + ) + }) + + it('should successfully deploy if valid JSON file was provided', async () => { + const deployContents = JSON.stringify({ + appLoc: '/public', + fileTree: getTreeExample() + }) + const res = await request(app) + .post('/SASjsApi/drive/deploy/upload') + .auth(accessToken, { type: 'bearer' }) + .attach('file', Buffer.from(deployContents), 'deploy.json') + + expect(res.statusCode).toEqual(200) + expect(res.text).toEqual( + '{"status":"success","message":"Files deployed successfully to @sasjs/server."}' + ) + await expect(folderExists(getFilesFolder())).resolves.toEqual(true) + + const testJobFolder = path.join( + getFilesFolder(), + 'public', + 'jobs', + 'extract' + ) + await expect(folderExists(testJobFolder)).resolves.toEqual(true) + + const exampleService = getExampleService() + const testJobFile = + path.join(testJobFolder, exampleService.name) + '.sas' + + await expect(fileExists(testJobFile)).resolves.toEqual(true) + + await expect(readFile(testJobFile)).resolves.toEqual( + exampleService.code + ) + + await deleteFolder(path.join(getFilesFolder(), 'public')) + }) + }) + + describe('upload - zipped', () => { + it('should respond with payload example if valid Zipped file was not provided', async () => { + await shouldFailAssertion(null, 'zip') + await shouldFailAssertion(undefined, 'zip') + await shouldFailAssertion('data', 'zip') + await shouldFailAssertion({}, 'zip') + await shouldFailAssertion( + { + userId: 1, + title: 'test is cool' + }, + 'zip' + ) + await shouldFailAssertion( + { + membersWRONG: [] + }, + 'zip' + ) + await shouldFailAssertion( + { + members: {} + }, + 'zip' + ) + await shouldFailAssertion( + { + members: [ + { + nameWRONG: 'jobs', + type: 'folder', + members: [] + } + ] + }, + 'zip' + ) + await shouldFailAssertion( + { + members: [ + { + name: 'jobs', + type: 'WRONG', + members: [] + } + ] + }, + 'zip' + ) + await shouldFailAssertion( + { + members: [ + { + name: 'jobs', + type: 'folder', + members: [ + { + name: 'extract', + type: 'folder', + members: [ + { + name: 'makedata1', + type: 'service', + codeWRONG: '%put Hello World!;' + } + ] + } + ] + } + ] + }, + 'zip' + ) + }) + + it('should successfully deploy if valid Zipped file was provided', async () => { + const deployContents = JSON.stringify({ + appLoc: '/public', + fileTree: getTreeExample() + }) + + const zip = new AdmZip() + // add file directly + zip.addFile( + 'deploy.json', + Buffer.from(deployContents, 'utf8'), + 'entry comment goes here' + ) + const res = await request(app) + .post('/SASjsApi/drive/deploy/upload') + .auth(accessToken, { type: 'bearer' }) + .attach('file', zip.toBuffer(), 'deploy.json.zip') + + expect(res.statusCode).toEqual(200) + expect(res.text).toEqual( + '{"status":"success","message":"Files deployed successfully to @sasjs/server."}' + ) + await expect(folderExists(getFilesFolder())).resolves.toEqual(true) + + const testJobFolder = path.join( + getFilesFolder(), + 'public', + 'jobs', + 'extract' + ) + await expect(folderExists(testJobFolder)).resolves.toEqual(true) + + const exampleService = getExampleService() + const testJobFile = + path.join(testJobFolder, exampleService.name) + '.sas' + + await expect(fileExists(testJobFile)).resolves.toEqual(true) + + await expect(readFile(testJobFile)).resolves.toEqual( + exampleService.code + ) + + await deleteFolder(path.join(getFilesFolder(), 'public')) + }) + }) }) describe('folder', () => { diff --git a/api/src/utils/zipped.ts b/api/src/utils/zipped.ts index 925d1b6..f90f79a 100644 --- a/api/src/utils/zipped.ts +++ b/api/src/utils/zipped.ts @@ -30,6 +30,7 @@ export const extractJSONFromZip = async (zipFile: Express.Multer.File) => { const fileName = entry.path as string if (fileName.toUpperCase().endsWith('.JSON') && fileName === fileInZip) { fileContent = await entry.buffer() + break } else { entry.autodrain() }