From a32c0879b3988b95d8eac3ca21d778f23787b3c0 Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Thu, 6 May 2021 12:44:08 +0100 Subject: [PATCH 1/7] fix(convert-to-csv): fix bug with escaping quoted string --- src/utils/convertToCsv.spec.ts | 27 +++++++++++++++++++++++++++ src/utils/convertToCsv.ts | 22 +++++++++------------- 2 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 src/utils/convertToCsv.spec.ts diff --git a/src/utils/convertToCsv.spec.ts b/src/utils/convertToCsv.spec.ts new file mode 100644 index 0000000..0faa848 --- /dev/null +++ b/src/utils/convertToCsv.spec.ts @@ -0,0 +1,27 @@ +import { convertToCSV } from './convertToCsv' + +describe('convertToCsv', () => { + it('should convert single quoted values', () => { + const data = [ + { foo: `'bar'`, bar: 'abc' }, + { foo: 'sadf', bar: 'def' }, + { foo: 'asd', bar: `'qwert'` } + ] + + const expectedOutput = `foo:$5. bar:$7.\r\n"'bar'",abc\r\nsadf,def\r\nasd,"'qwert'"` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) + + it('should convert double quoted values', () => { + const data = [ + { foo: `"bar"`, bar: 'abc' }, + { foo: 'sadf', bar: 'def' }, + { foo: 'asd', bar: `"qwert"` } + ] + + const expectedOutput = `foo:$5. bar:$7.\r\n"""bar""",abc\r\nsadf,def\r\nasd,"""qwert"""` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) +}) diff --git a/src/utils/convertToCsv.ts b/src/utils/convertToCsv.ts index 08a33b1..c071ffa 100644 --- a/src/utils/convertToCsv.ts +++ b/src/utils/convertToCsv.ts @@ -1,6 +1,6 @@ /** - * Converts the given JSON object to a CSV string. - * @param data - the JSON object to convert. + * Converts the given JSON object array to a CSV string. + * @param data - the array of JSON objects to convert. */ export const convertToCSV = (data: any) => { const replacer = (key: any, value: any) => (value === null ? '' : value) @@ -37,15 +37,7 @@ export const convertToCSV = (data: any) => { let byteSize if (typeof row[field] === 'string') { - let doubleQuotesFound = row[field] - .split('') - .filter((char: any) => char === '"') - byteSize = getByteSize(row[field]) - - if (doubleQuotesFound.length > 0) { - byteSize += doubleQuotesFound.length - } } return byteSize @@ -73,6 +65,7 @@ export const convertToCSV = (data: any) => { if (invalidString) { return 'ERROR: LARGE STRING LENGTH' } + csvTest = data.map((row: any) => { const fields = Object.keys(row).map((fieldName, index) => { let value @@ -80,12 +73,11 @@ export const convertToCSV = (data: any) => { const currentCell = row[fieldName] if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) { - value = currentCell.toString() containsSpecialChar = true - } else { - value = JSON.stringify(currentCell, replacer) } + value = JSON.stringify(currentCell, replacer) + value = value.replace(/\\\\/gm, '\\') if (containsSpecialChar) { @@ -101,6 +93,10 @@ export const convertToCSV = (data: any) => { value = value.substring(1, value.length - 1) } + if (value.includes("'")) { + value = '"' + value + '"' + } + value = value.replace(/\\"/gm, '""') } From 5543f467e6a9bce655a2451d01ee547d35a3bea7 Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Thu, 6 May 2021 13:21:49 +0100 Subject: [PATCH 2/7] chore(*): add more test cases --- src/utils/convertToCsv.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/utils/convertToCsv.spec.ts b/src/utils/convertToCsv.spec.ts index 0faa848..e1f8f85 100644 --- a/src/utils/convertToCsv.spec.ts +++ b/src/utils/convertToCsv.spec.ts @@ -24,4 +24,20 @@ describe('convertToCsv', () => { expect(convertToCSV(data)).toEqual(expectedOutput) }) + + it('should convert values with mixed quotes', () => { + const data = [{ foo: `'blah'`, bar: `"blah"` }] + + const expectedOutput = `foo:$6. bar:$6.\r\n"'blah'","""blah"""` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) + + it('should convert values with mixed quotes', () => { + const data = [{ foo: `'blah,"'`, bar: `"blah,blah" "` }] + + const expectedOutput = `foo:$8. bar:$13.\r\n"'blah,""'","""blah,blah"" """` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) }) From 665734b1689518215c4939efa49f9ba174bce508 Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Thu, 6 May 2021 13:59:33 +0100 Subject: [PATCH 3/7] chore(*): add tests --- src/utils/convertToCsv.spec.ts | 32 ++++++++++++++++++++++++++++++++ src/utils/convertToCsv.ts | 33 +++++++++------------------------ 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/utils/convertToCsv.spec.ts b/src/utils/convertToCsv.spec.ts index e1f8f85..aad5146 100644 --- a/src/utils/convertToCsv.spec.ts +++ b/src/utils/convertToCsv.spec.ts @@ -40,4 +40,36 @@ describe('convertToCsv', () => { expect(convertToCSV(data)).toEqual(expectedOutput) }) + + it('should convert values with mixed quotes', () => { + const data = [{ foo: `',''`, bar: `","` }] + + const expectedOutput = `foo:$4. bar:$3.\r\n"',''",""","""` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) + + it('should convert values with mixed quotes', () => { + const data = [{ foo: `','`, bar: `,"` }] + + const expectedOutput = `foo:$3. bar:$2.\r\n"','",","""` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) + + it('should convert values with mixed quotes', () => { + const data = [{ foo: `"`, bar: `'` }] + + const expectedOutput = `foo:$1. bar:$1.\r\n"""","'"` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) + + it('should convert values with mixed quotes', () => { + const data = [{ foo: `,`, bar: `',` }] + + const expectedOutput = `foo:$1. bar:$2.\r\n",","',"` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) }) diff --git a/src/utils/convertToCsv.ts b/src/utils/convertToCsv.ts index c071ffa..d6d0841 100644 --- a/src/utils/convertToCsv.ts +++ b/src/utils/convertToCsv.ts @@ -69,35 +69,20 @@ export const convertToCSV = (data: any) => { csvTest = data.map((row: any) => { const fields = Object.keys(row).map((fieldName, index) => { let value - let containsSpecialChar = false const currentCell = row[fieldName] - if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) { - containsSpecialChar = true - } - - value = JSON.stringify(currentCell, replacer) + // stringify with replacer converts null values to empty strings + // also wraps the value in double quotes + value = JSON.stringify(currentCell, replacer).replace(/\\\"/g, `""`) value = value.replace(/\\\\/gm, '\\') - if (containsSpecialChar) { - if (value.includes(',') || value.includes('"')) { - value = '"' + value + '"' - } - } else { - if ( - !value.includes(',') && - value.includes('"') && - !value.includes('\\"') - ) { - value = value.substring(1, value.length - 1) - } - - if (value.includes("'")) { - value = '"' + value + '"' - } - - value = value.replace(/\\"/gm, '""') + if ( + value.substring(1, value.length - 1).search(/(\\t|\\n|\\r|,|\'|\")/gm) < + 0 + ) { + // Remove wrapping quotes for values that don't contain special characters + value = value.substring(1, value.length - 1) } value = value.replace(/\r\n/gm, '\n') From 80e5de5d653efa11966bb4b5f3d696bfebc5c69c Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 6 May 2021 22:28:06 +0500 Subject: [PATCH 4/7] fix: master tests fixed --- sasjs-tests/package-lock.json | 97 +++++++++++++++++++---- sasjs-tests/package.json | 6 +- sasjs-tests/src/testSuites/Basic.ts | 17 ++-- sasjs-tests/src/testSuites/RequestData.ts | 11 ++- 4 files changed, 104 insertions(+), 27 deletions(-) diff --git a/sasjs-tests/package-lock.json b/sasjs-tests/package-lock.json index 9518e59..4fbb5db 100644 --- a/sasjs-tests/package-lock.json +++ b/sasjs-tests/package-lock.json @@ -2005,18 +2005,18 @@ }, "@sasjs/adapter": { "version": "file:../build/sasjs-adapter-5.0.0.tgz", - "integrity": "sha512-1t+3LIL2BFw8HpZUPI9QM24801+JH4DCAu4eHoLLmytYhN72asMi1aVtgSDb1xiJYgpbTG7EK3qRpHIV8cEN8w==", + "integrity": "sha512-M7R1F4gBHZRjDD/lI3v3v9jnX7Lvhp3W1xD/0imKaLt7LZfth1VSLf4umwwajmlRUlktJ4ht6dugXqZLOh+4wQ==", "requires": { - "@sasjs/utils": "^2.5.0", + "@sasjs/utils": "^2.10.2", "axios": "^0.21.1", - "form-data": "^3.0.0", + "form-data": "^4.0.0", "https": "^1.0.0" }, "dependencies": { "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2046,14 +2046,70 @@ } }, "@sasjs/utils": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.5.1.tgz", - "integrity": "sha512-a3ISiUX8Yz7au4XYxq2KWf9ODT6nsIDbE4FEqS+AQ3McxZkfuAk4v+REXjOmIlcyQd4R4bufEK8XoB6AROn9sA==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.12.1.tgz", + "integrity": "sha512-6gZS5zW0J70P7XaVuEczyfHVaVa8Ks/aWr4PIlpJcxWD0enJtCEmos2DdnezdSoNvODkPq/8rzMPqko5jaXK1Q==", "requires": { - "@types/prompts": "^2.0.9", + "@types/prompts": "^2.0.11", + "chalk": "^4.1.1", + "cli-table": "^0.3.6", "consola": "^2.15.0", - "prompts": "^2.4.0", + "prompts": "^2.4.1", "valid-url": "^1.0.9" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "prompts": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", + "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@semantic-ui-react/event-stack": { @@ -2366,9 +2422,9 @@ "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==" }, "@types/prompts": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.9.tgz", - "integrity": "sha512-TORZP+FSjTYMWwKadftmqEn6bziN5RnfygehByGsjxoK5ydnClddtv6GikGWPvCm24oI+YBwck5WDxIIyNxUrA==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.11.tgz", + "integrity": "sha512-dcF5L3rU9VfpLEJIV++FEyhGhuIpJllNEwllVuJ5g8eoVqjf048tW9+spivIwjzgPbtaGAl7mIZW3cmhDAq2UQ==", "requires": { "@types/node": "*" } @@ -4460,6 +4516,14 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, + "cli-table": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", + "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", + "requires": { + "colors": "1.0.3" + } + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -4568,6 +4632,11 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", diff --git a/sasjs-tests/package.json b/sasjs-tests/package.json index ab400ec..1c5e69b 100644 --- a/sasjs-tests/package.json +++ b/sasjs-tests/package.json @@ -4,7 +4,7 @@ "homepage": ".", "private": true, "dependencies": { - "@sasjs/adapter": "^2.2.4", + "@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz", "@sasjs/test-framework": "^1.4.0", "@types/jest": "^26.0.20", "@types/node": "^14.14.25", @@ -23,8 +23,8 @@ "test": "react-scripts test", "eject": "react-scripts eject", "update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz", - "deploy:tests": "npm run build && rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH", - "deploy": "npm run update:adapter && npm run deploy:tests" + "deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH", + "deploy": "npm run update:adapter && npm run build && npm run deploy:tests" }, "eslintConfig": { "extends": "react-app" diff --git a/sasjs-tests/src/testSuites/Basic.ts b/sasjs-tests/src/testSuites/Basic.ts index f7faf73..a79e812 100644 --- a/sasjs-tests/src/testSuites/Basic.ts +++ b/sasjs-tests/src/testSuites/Basic.ts @@ -59,10 +59,15 @@ export const basicTests = ( "Should trigger required login callback and after successful login, it should finish the request", test: async () => { await adapter.logOut(); - - return await adapter.request("common/sendArr", stringData, null, () => { - adapter.logIn(userName, password); - }); + + return await adapter.request( + "common/sendArr", + stringData, + undefined, + () => { + adapter.logIn(userName, password); + } + ); }, assertion: (response: any) => { return response.table1[0][0] === stringData.table1[0].col1; @@ -75,9 +80,9 @@ export const basicTests = ( test: async () => { const config = { debug: true - } + }; - return await adapter.request("common/sendArr", stringData, config) + return await adapter.request("common/sendArr", stringData, config); }, assertion: (response: any) => { return response.table1[0][0] === stringData.table1[0].col1; diff --git a/sasjs-tests/src/testSuites/RequestData.ts b/sasjs-tests/src/testSuites/RequestData.ts index 24c33fb..11985b7 100644 --- a/sasjs-tests/src/testSuites/RequestData.ts +++ b/sasjs-tests/src/testSuites/RequestData.ts @@ -15,6 +15,7 @@ const multipleRowsWithNulls: any = { { col1: 42, col2: 1.62, col3: "x", col4: "x" } ] }; + const multipleColumnsWithNulls: any = { table1: [ { col1: 42, col2: null, col3: "x", col4: null }, @@ -136,7 +137,8 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({ res.table1[index][2] === multipleRowsWithNulls.table1[index].col3; result = result && - res.table1[index][3] === multipleRowsWithNulls.table1[index].col4; + res.table1[index][3] === + (multipleRowsWithNulls.table1[index].col4 || " "); }); return result; } @@ -165,7 +167,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({ result = result && res.table1[index][3] === - (multipleColumnsWithNulls.table1[index].col4 || ""); + (multipleColumnsWithNulls.table1[index].col4 || " "); }); return result; } @@ -280,7 +282,8 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({ res.table1[index].COL3 === multipleRowsWithNulls.table1[index].col3; result = result && - res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4; + res.table1[index].COL4 === + (multipleRowsWithNulls.table1[index].col4 || " "); }); return result; } @@ -309,7 +312,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({ result = result && res.table1[index].COL4 === - (multipleColumnsWithNulls.table1[index].col4 || ""); + (multipleColumnsWithNulls.table1[index].col4 || " "); }); return result; } From 88f08e8864bf25a2a609b1d6c0a4daacff25558b Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Fri, 7 May 2021 05:26:08 +0500 Subject: [PATCH 5/7] fix: removed extra slash + added tests --- src/utils/convertToCsv.spec.ts | 93 ++++++++++++++++++++++++++++++++++ src/utils/convertToCsv.ts | 16 +++--- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/utils/convertToCsv.spec.ts b/src/utils/convertToCsv.spec.ts index aad5146..b6e48bd 100644 --- a/src/utils/convertToCsv.spec.ts +++ b/src/utils/convertToCsv.spec.ts @@ -72,4 +72,97 @@ describe('convertToCsv', () => { expect(convertToCSV(data)).toEqual(expectedOutput) }) + + it('should convert values with number cases 1', () => { + const data = [ + { col1: 42, col2: null, col3: 'x', col4: null }, + { col1: 42, col2: null, col3: 'x', col4: null }, + { col1: 42, col2: null, col3: 'x', col4: null }, + { col1: 42, col2: null, col3: 'x', col4: '' }, + { col1: 42, col2: null, col3: 'x', col4: '' } + ] + + const expectedOutput = `col1:best. col2:best. col3:$1. col4:$1.\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) + + it('should convert values with number cases 2', () => { + const data = [ + { col1: 42, col2: null, col3: 'x', col4: '' }, + { col1: 42, col2: null, col3: 'x', col4: '' }, + { col1: 42, col2: null, col3: 'x', col4: '' }, + { col1: 42, col2: 1.62, col3: 'x', col4: 'x' }, + { col1: 42, col2: 1.62, col3: 'x', col4: 'x' } + ] + + const expectedOutput = `col1:best. col2:best. col3:$1. col4:$1.\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,1.62,x,x\r\n42,1.62,x,x` + + expect(convertToCSV(data)).toEqual(expectedOutput) + }) + + it('should convert values with common special characters', () => { + expect(convertToCSV([{ tab: '\t' }])).toEqual(`tab:$1.\r\n\"\t\"`) + expect(convertToCSV([{ lf: '\n' }])).toEqual(`lf:$1.\r\n\"\n\"`) + expect(convertToCSV([{ semicolon: ';semi' }])).toEqual( + `semicolon:$5.\r\n;semi` + ) + expect(convertToCSV([{ percent: '%' }])).toEqual(`percent:$1.\r\n%`) + expect(convertToCSV([{ singleQuote: "'" }])).toEqual( + `singleQuote:$1.\r\n\"'\"` + ) + expect(convertToCSV([{ doubleQuote: '"' }])).toEqual( + `doubleQuote:$1.\r\n""""` + ) + expect(convertToCSV([{ crlf: '\r\n' }])).toEqual(`crlf:$2.\r\n\"\n\"`) + expect(convertToCSV([{ euro: '€euro' }])).toEqual(`euro:$7.\r\n€euro`) + expect(convertToCSV([{ banghash: '!#banghash' }])).toEqual( + `banghash:$10.\r\n!#banghash` + ) + }) + + it('should convert values with other special characters', () => { + const data = [ + { + speech0: '"speech', + pct: '%percent', + speech: '"speech', + slash: '\\slash', + slashWithSpecial: '\\\tslash', + macvar: '&sysuserid', + chinese: '传/傳chinese', + sigma: 'Σsigma', + at: '@at', + serbian: 'Српски', + dollar: '$' + } + ] + + const expectedOutput = `speech0:$7. pct:$8. speech:$7. slash:$6. slashWithSpecial:$7. macvar:$10. chinese:$14. sigma:$7. at:$3. serbian:$12. dollar:$1.\r\n"""speech",%percent,"""speech",\\slash,\"\\\tslash\",&sysuserid,传/傳chinese,Σsigma,@at,Српски,$` + + expect(convertToCSV(data)).toEqual(expectedOutput) + + expect(convertToCSV([{ speech: 'menext' }])).toEqual(`speech:$6.\r\nmenext`) + expect(convertToCSV([{ speech: 'me\nnext' }])).toEqual( + `speech:$7.\r\n\"me\nnext\"` + ) + expect(convertToCSV([{ speech: `me'next` }])).toEqual( + `speech:$7.\r\n\"me'next\"` + ) + expect(convertToCSV([{ speech: `me"next` }])).toEqual( + `speech:$7.\r\n\"me""next\"` + ) + expect(convertToCSV([{ speech: `me""next` }])).toEqual( + `speech:$8.\r\n\"me""""next\"` + ) + expect(convertToCSV([{ slashWithSpecial: '\\\tslash' }])).toEqual( + `slashWithSpecial:$7.\r\n\"\\\tslash\"` + ) + expect(convertToCSV([{ slashWithSpecial: '\\ \tslash' }])).toEqual( + `slashWithSpecial:$8.\r\n\"\\ \tslash\"` + ) + expect( + convertToCSV([{ slashWithSpecialExtra: '\\\ts\tl\ta\ts\t\th\t' }]) + ).toEqual(`slashWithSpecialExtra:$13.\r\n\"\\\ts\tl\ta\ts\t\th\t\"`) + }) }) diff --git a/src/utils/convertToCsv.ts b/src/utils/convertToCsv.ts index d6d0841..8260a93 100644 --- a/src/utils/convertToCsv.ts +++ b/src/utils/convertToCsv.ts @@ -71,15 +71,19 @@ export const convertToCSV = (data: any) => { let value const currentCell = row[fieldName] - // stringify with replacer converts null values to empty strings - // also wraps the value in double quotes - value = JSON.stringify(currentCell, replacer).replace(/\\\"/g, `""`) + if (typeof currentCell === 'number') return currentCell - value = value.replace(/\\\\/gm, '\\') + // stringify with replacer converts null values to empty strings + value = currentCell === null ? '' : currentCell + + // if there any present, it should have preceding (") for escaping + value = value.replace(/"/g, `""`) + + // also wraps the value in double quotes + value = `"${value}"` if ( - value.substring(1, value.length - 1).search(/(\\t|\\n|\\r|,|\'|\")/gm) < - 0 + value.substring(1, value.length - 1).search(/(\t|\n|\r|,|\'|\")/gm) < 0 ) { // Remove wrapping quotes for values that don't contain special characters value = value.substring(1, value.length - 1) From b86658ef9b98ef8845262ef2d909c0146c8f8ca3 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Fri, 7 May 2021 10:30:27 +0300 Subject: [PATCH 6/7] fix(csv-convert): fixed data convertion --- package.json | 4 +- sasjs-tests/.prettierrc | 4 +- sasjs-tests/package-lock.json | 2 +- sasjs-tests/src/App.tsx | 30 +-- sasjs-tests/src/Login.tsx | 28 +-- sasjs-tests/src/PrivateRoute.tsx | 22 +- sasjs-tests/src/index.tsx | 24 +- sasjs-tests/src/serviceWorker.js | 84 +++---- sasjs-tests/src/setupTests.js | 2 +- sasjs-tests/src/testSuites/Basic.ts | 106 ++++---- sasjs-tests/src/testSuites/Compute.ts | 96 ++++--- sasjs-tests/src/testSuites/RequestData.ts | 261 ++++++++++---------- sasjs-tests/src/testSuites/SasjsRequests.ts | 42 ++-- sasjs-tests/src/testSuites/SpecialCases.ts | 186 +++++++------- sasjs-tests/src/utils/Assert.ts | 22 +- src/utils/convertToCsv.ts | 2 +- 16 files changed, 454 insertions(+), 461 deletions(-) diff --git a/package.json b/package.json index 8187ac8..0ad66e4 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node", "package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack", "publish:lib": "npm run build && cd build && npm publish", - "lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'", - "lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'", + "lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}' && npx prettier --write 'sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'", + "lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}' && npx prettier --check 'sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'", "test": "jest --silent --coverage", "prepublishOnly": "cp -r ./build/* . && rm -rf ./build", "postpublish": "git clean -fd", diff --git a/sasjs-tests/.prettierrc b/sasjs-tests/.prettierrc index fca24df..c50384f 100644 --- a/sasjs-tests/.prettierrc +++ b/sasjs-tests/.prettierrc @@ -1,6 +1,6 @@ { "trailingComma": "none", "tabWidth": 2, - "semi": true, - "singleQuote": false + "semi": false, + "singleQuote": true } diff --git a/sasjs-tests/package-lock.json b/sasjs-tests/package-lock.json index 4fbb5db..d198432 100644 --- a/sasjs-tests/package-lock.json +++ b/sasjs-tests/package-lock.json @@ -2005,7 +2005,7 @@ }, "@sasjs/adapter": { "version": "file:../build/sasjs-adapter-5.0.0.tgz", - "integrity": "sha512-M7R1F4gBHZRjDD/lI3v3v9jnX7Lvhp3W1xD/0imKaLt7LZfth1VSLf4umwwajmlRUlktJ4ht6dugXqZLOh+4wQ==", + "integrity": "sha512-DxoQbdJqzqOTIuT7qwSfAbmNTWdpOx5zGkiMuZBSwoi9lSsRNoARiWnJq5Vl6h4RXJlc/FVdBFt35RZm4Mc0ZQ==", "requires": { "@sasjs/utils": "^2.10.2", "axios": "^0.21.1", diff --git a/sasjs-tests/src/App.tsx b/sasjs-tests/src/App.tsx index 223f952..e360241 100644 --- a/sasjs-tests/src/App.tsx +++ b/sasjs-tests/src/App.tsx @@ -1,15 +1,15 @@ -import React, { ReactElement, useState, useContext, useEffect } from "react"; -import { TestSuiteRunner, TestSuite, AppContext } from "@sasjs/test-framework"; -import { basicTests } from "./testSuites/Basic"; -import { sendArrTests, sendObjTests } from "./testSuites/RequestData"; -import { specialCaseTests } from "./testSuites/SpecialCases"; -import { sasjsRequestTests } from "./testSuites/SasjsRequests"; -import "@sasjs/test-framework/dist/index.css"; -import { computeTests } from "./testSuites/Compute"; +import React, { ReactElement, useState, useContext, useEffect } from 'react' +import { TestSuiteRunner, TestSuite, AppContext } from '@sasjs/test-framework' +import { basicTests } from './testSuites/Basic' +import { sendArrTests, sendObjTests } from './testSuites/RequestData' +import { specialCaseTests } from './testSuites/SpecialCases' +import { sasjsRequestTests } from './testSuites/SasjsRequests' +import '@sasjs/test-framework/dist/index.css' +import { computeTests } from './testSuites/Compute' const App = (): ReactElement<{}> => { - const { adapter, config } = useContext(AppContext); - const [testSuites, setTestSuites] = useState([]); + const { adapter, config } = useContext(AppContext) + const [testSuites, setTestSuites] = useState([]) useEffect(() => { if (adapter) { @@ -20,15 +20,15 @@ const App = (): ReactElement<{}> => { specialCaseTests(adapter), sasjsRequestTests(adapter), computeTests(adapter) - ]); + ]) } - }, [adapter, config]); + }, [adapter, config]) return (
{adapter && testSuites && }
- ); -}; + ) +} -export default App; +export default App diff --git a/sasjs-tests/src/Login.tsx b/sasjs-tests/src/Login.tsx index 284f2d4..a72b458 100644 --- a/sasjs-tests/src/Login.tsx +++ b/sasjs-tests/src/Login.tsx @@ -1,22 +1,22 @@ -import React, { ReactElement, useState, useCallback, useContext } from "react"; -import "./Login.scss"; -import { AppContext } from "@sasjs/test-framework"; -import { Redirect } from "react-router-dom"; +import React, { ReactElement, useState, useCallback, useContext } from 'react' +import './Login.scss' +import { AppContext } from '@sasjs/test-framework' +import { Redirect } from 'react-router-dom' const Login = (): ReactElement<{}> => { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const appContext = useContext(AppContext); + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const appContext = useContext(AppContext) const handleSubmit = useCallback( (e) => { - e.preventDefault(); + e.preventDefault() appContext.adapter.logIn(username, password).then((res) => { - appContext.setIsLoggedIn(res.isLoggedIn); - }); + appContext.setIsLoggedIn(res.isLoggedIn) + }) }, [username, password, appContext] - ); + ) return !appContext.isLoggedIn ? (
@@ -48,7 +48,7 @@ const Login = (): ReactElement<{}> => {
) : ( - ); -}; + ) +} -export default Login; +export default Login diff --git a/sasjs-tests/src/PrivateRoute.tsx b/sasjs-tests/src/PrivateRoute.tsx index 8420955..b873dd9 100644 --- a/sasjs-tests/src/PrivateRoute.tsx +++ b/sasjs-tests/src/PrivateRoute.tsx @@ -1,23 +1,23 @@ -import React, { ReactElement, useContext, FunctionComponent } from "react"; -import { Redirect, Route } from "react-router-dom"; -import { AppContext } from "@sasjs/test-framework"; +import React, { ReactElement, useContext, FunctionComponent } from 'react' +import { Redirect, Route } from 'react-router-dom' +import { AppContext } from '@sasjs/test-framework' interface PrivateRouteProps { - component: FunctionComponent; - exact?: boolean; - path: string; + component: FunctionComponent + exact?: boolean + path: string } const PrivateRoute = ( props: PrivateRouteProps ): ReactElement => { - const { component, path, exact } = props; - const appContext = useContext(AppContext); + const { component, path, exact } = props + const appContext = useContext(AppContext) return appContext.isLoggedIn ? ( ) : ( - ); -}; + ) +} -export default PrivateRoute; +export default PrivateRoute diff --git a/sasjs-tests/src/index.tsx b/sasjs-tests/src/index.tsx index effc537..3b51af1 100644 --- a/sasjs-tests/src/index.tsx +++ b/sasjs-tests/src/index.tsx @@ -1,12 +1,12 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import { Route, HashRouter, Switch } from "react-router-dom"; -import "./index.scss"; -import * as serviceWorker from "./serviceWorker"; -import { AppProvider } from "@sasjs/test-framework"; -import PrivateRoute from "./PrivateRoute"; -import Login from "./Login"; -import App from "./App"; +import React from 'react' +import ReactDOM from 'react-dom' +import { Route, HashRouter, Switch } from 'react-router-dom' +import './index.scss' +import * as serviceWorker from './serviceWorker' +import { AppProvider } from '@sasjs/test-framework' +import PrivateRoute from './PrivateRoute' +import Login from './Login' +import App from './App' ReactDOM.render( @@ -17,10 +17,10 @@ ReactDOM.render( , - document.getElementById("root") -); + document.getElementById('root') +) // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); +serviceWorker.unregister() diff --git a/sasjs-tests/src/serviceWorker.js b/sasjs-tests/src/serviceWorker.js index 58bd4c6..493867a 100644 --- a/sasjs-tests/src/serviceWorker.js +++ b/sasjs-tests/src/serviceWorker.js @@ -11,46 +11,46 @@ // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( - window.location.hostname === "localhost" || + window.location.hostname === 'localhost' || // [::1] is the IPv6 localhost address. - window.location.hostname === "[::1]" || + window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. window.location.hostname.match( /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ ) -); +) export function register(config) { - if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href) if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; + return } - window.addEventListener("load", () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + checkValidServiceWorker(swUrl, config) // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( - "This web app is being served cache-first by a service " + - "worker. To learn more, visit https://bit.ly/CRA-PWA" - ); - }); + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ) + }) } else { // Is not localhost. Just register service worker - registerValidSW(swUrl, config); + registerValidSW(swUrl, config) } - }); + }) } } @@ -59,83 +59,83 @@ function registerValidSW(swUrl, config) { .register(swUrl) .then((registration) => { registration.onupdatefound = () => { - const installingWorker = registration.installing; + const installingWorker = registration.installing if (installingWorker == null) { - return; + return } installingWorker.onstatechange = () => { - if (installingWorker.state === "installed") { + if (installingWorker.state === 'installed') { if (navigator.serviceWorker.controller) { // At this point, the updated precached content has been fetched, // but the previous service worker will still serve the older // content until all client tabs are closed. console.log( - "New content is available and will be used when all " + - "tabs for this page are closed. See https://bit.ly/CRA-PWA." - ); + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ) // Execute callback if (config && config.onUpdate) { - config.onUpdate(registration); + config.onUpdate(registration) } } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. - console.log("Content is cached for offline use."); + console.log('Content is cached for offline use.') // Execute callback if (config && config.onSuccess) { - config.onSuccess(registration); + config.onSuccess(registration) } } } - }; - }; + } + } }) .catch((error) => { - console.error("Error during service worker registration:", error); - }); + console.error('Error during service worker registration:', error) + }) } function checkValidServiceWorker(swUrl, config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { - headers: { "Service-Worker": "script" } + headers: { 'Service-Worker': 'script' } }) .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get("content-type"); + const contentType = response.headers.get('content-type') if ( response.status === 404 || - (contentType != null && contentType.indexOf("javascript") === -1) + (contentType != null && contentType.indexOf('javascript') === -1) ) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { - window.location.reload(); - }); - }); + window.location.reload() + }) + }) } else { // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); + registerValidSW(swUrl, config) } }) .catch(() => { console.log( - "No internet connection found. App is running in offline mode." - ); - }); + 'No internet connection found. App is running in offline mode.' + ) + }) } export function unregister() { - if ("serviceWorker" in navigator) { + if ('serviceWorker' in navigator) { navigator.serviceWorker.ready .then((registration) => { - registration.unregister(); + registration.unregister() }) .catch((error) => { - console.error(error.message); - }); + console.error(error.message) + }) } } diff --git a/sasjs-tests/src/setupTests.js b/sasjs-tests/src/setupTests.js index 5fdf001..2eb59b0 100644 --- a/sasjs-tests/src/setupTests.js +++ b/sasjs-tests/src/setupTests.js @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import "@testing-library/jest-dom/extend-expect"; +import '@testing-library/jest-dom/extend-expect' diff --git a/sasjs-tests/src/testSuites/Basic.ts b/sasjs-tests/src/testSuites/Basic.ts index a79e812..e1b5887 100644 --- a/sasjs-tests/src/testSuites/Basic.ts +++ b/sasjs-tests/src/testSuites/Basic.ts @@ -1,102 +1,102 @@ -import SASjs, { SASjsConfig } from "@sasjs/adapter"; -import { TestSuite } from "@sasjs/test-framework"; -import { ServerType } from "@sasjs/utils/types"; +import SASjs, { SASjsConfig } from '@sasjs/adapter' +import { TestSuite } from '@sasjs/test-framework' +import { ServerType } from '@sasjs/utils/types' -const stringData: any = { table1: [{ col1: "first col value" }] }; +const stringData: any = { table1: [{ col1: 'first col value' }] } const defaultConfig: SASjsConfig = { serverUrl: window.location.origin, - pathSAS9: "/SASStoredProcess/do", - pathSASViya: "/SASJobExecution", - appLoc: "/Public/seedapp", + pathSAS9: '/SASStoredProcess/do', + pathSASViya: '/SASJobExecution', + appLoc: '/Public/seedapp', serverType: ServerType.SasViya, debug: false, - contextName: "SAS Job Execution compute context", + contextName: 'SAS Job Execution compute context', useComputeApi: false, allowInsecureRequests: false -}; +} const customConfig = { - serverUrl: "http://url.com", - pathSAS9: "sas9", - pathSASViya: "viya", - appLoc: "/Public/seedapp", + serverUrl: 'http://url.com', + pathSAS9: 'sas9', + pathSASViya: 'viya', + appLoc: '/Public/seedapp', serverType: ServerType.Sas9, debug: false -}; +} export const basicTests = ( adapter: SASjs, userName: string, password: string ): TestSuite => ({ - name: "Basic Tests", + name: 'Basic Tests', tests: [ { - title: "Log in", - description: "Should log the user in", + title: 'Log in', + description: 'Should log the user in', test: async () => { - return adapter.logIn(userName, password); + return adapter.logIn(userName, password) }, assertion: (response: any) => response && response.isLoggedIn && response.userName === userName }, { - title: "Multiple Log in attempts", + title: 'Multiple Log in attempts', description: - "Should fail on first attempt and should log the user in on second attempt", + 'Should fail on first attempt and should log the user in on second attempt', test: async () => { - await adapter.logOut(); - await adapter.logIn("invalid", "invalid"); - return adapter.logIn(userName, password); + await adapter.logOut() + await adapter.logIn('invalid', 'invalid') + return adapter.logIn(userName, password) }, assertion: (response: any) => response && response.isLoggedIn && response.userName === userName }, { - title: "Trigger login callback", + title: 'Trigger login callback', description: - "Should trigger required login callback and after successful login, it should finish the request", + 'Should trigger required login callback and after successful login, it should finish the request', test: async () => { - await adapter.logOut(); + await adapter.logOut() return await adapter.request( - "common/sendArr", + 'common/sendArr', stringData, undefined, () => { - adapter.logIn(userName, password); + adapter.logIn(userName, password) } - ); + ) }, assertion: (response: any) => { - return response.table1[0][0] === stringData.table1[0].col1; + return response.table1[0][0] === stringData.table1[0].col1 } }, { - title: "Request with debug on", + title: 'Request with debug on', description: - "Should complete successful request with debugging switched on", + 'Should complete successful request with debugging switched on', test: async () => { const config = { debug: true - }; + } - return await adapter.request("common/sendArr", stringData, config); + return await adapter.request('common/sendArr', stringData, config) }, assertion: (response: any) => { - return response.table1[0][0] === stringData.table1[0].col1; + return response.table1[0][0] === stringData.table1[0].col1 } }, { - title: "Default config", + title: 'Default config', description: - "Should instantiate with default config when none is provided", + 'Should instantiate with default config when none is provided', test: async () => { - return Promise.resolve(new SASjs()); + return Promise.resolve(new SASjs()) }, assertion: (sasjsInstance: SASjs) => { - const sasjsConfig = sasjsInstance.getSasjsConfig(); + const sasjsConfig = sasjsInstance.getSasjsConfig() return ( sasjsConfig.serverUrl === defaultConfig.serverUrl && @@ -105,17 +105,17 @@ export const basicTests = ( sasjsConfig.appLoc === defaultConfig.appLoc && sasjsConfig.serverType === defaultConfig.serverType && sasjsConfig.debug === defaultConfig.debug - ); + ) } }, { - title: "Custom config", - description: "Should use fully custom config whenever supplied", + title: 'Custom config', + description: 'Should use fully custom config whenever supplied', test: async () => { - return Promise.resolve(new SASjs(customConfig)); + return Promise.resolve(new SASjs(customConfig)) }, assertion: (sasjsInstance: SASjs) => { - const sasjsConfig = sasjsInstance.getSasjsConfig(); + const sasjsConfig = sasjsInstance.getSasjsConfig() return ( sasjsConfig.serverUrl === customConfig.serverUrl && sasjsConfig.pathSAS9 === customConfig.pathSAS9 && @@ -123,28 +123,28 @@ export const basicTests = ( sasjsConfig.appLoc === customConfig.appLoc && sasjsConfig.serverType === customConfig.serverType && sasjsConfig.debug === customConfig.debug - ); + ) } }, { - title: "Config overrides", - description: "Should override default config with supplied properties", + title: 'Config overrides', + description: 'Should override default config with supplied properties', test: async () => { return Promise.resolve( - new SASjs({ serverUrl: "http://test.com", debug: false }) - ); + new SASjs({ serverUrl: 'http://test.com', debug: false }) + ) }, assertion: (sasjsInstance: SASjs) => { - const sasjsConfig = sasjsInstance.getSasjsConfig(); + const sasjsConfig = sasjsInstance.getSasjsConfig() return ( - sasjsConfig.serverUrl === "http://test.com" && + sasjsConfig.serverUrl === 'http://test.com' && sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 && sasjsConfig.pathSASViya === defaultConfig.pathSASViya && sasjsConfig.appLoc === defaultConfig.appLoc && sasjsConfig.serverType === defaultConfig.serverType && sasjsConfig.debug === false - ); + ) } } ] -}); +}) diff --git a/sasjs-tests/src/testSuites/Compute.ts b/sasjs-tests/src/testSuites/Compute.ts index b2cf050..afbd73b 100644 --- a/sasjs-tests/src/testSuites/Compute.ts +++ b/sasjs-tests/src/testSuites/Compute.ts @@ -1,106 +1,100 @@ -import SASjs from "@sasjs/adapter"; -import { TestSuite } from "@sasjs/test-framework"; +import SASjs from '@sasjs/adapter' +import { TestSuite } from '@sasjs/test-framework' export const computeTests = (adapter: SASjs): TestSuite => ({ - name: "Compute", + name: 'Compute', tests: [ { - title: "Start Compute Job - not waiting for result", - description: "Should start a compute job and return the session", + title: 'Start Compute Job - not waiting for result', + description: 'Should start a compute job and return the session', test: () => { - const data: any = { table1: [{ col1: "first col value" }] }; - return adapter.startComputeJob("/Public/app/common/sendArr", data); + const data: any = { table1: [{ col1: 'first col value' }] } + return adapter.startComputeJob('/Public/app/common/sendArr', data) }, assertion: (res: any) => { - const expectedProperties = ["id", "applicationName", "attributes"]; - return validate(expectedProperties, res); + const expectedProperties = ['id', 'applicationName', 'attributes'] + return validate(expectedProperties, res) } }, { - title: "Start Compute Job - waiting for result", - description: "Should start a compute job and return the job", + title: 'Start Compute Job - waiting for result', + description: 'Should start a compute job and return the job', test: () => { - const data: any = { table1: [{ col1: "first col value" }] }; + const data: any = { table1: [{ col1: 'first col value' }] } return adapter.startComputeJob( - "/Public/app/common/sendArr", + '/Public/app/common/sendArr', data, {}, - "", + '', true - ); + ) }, assertion: (res: any) => { const expectedProperties = [ - "id", - "state", - "creationTimeStamp", - "jobConditionCode" - ]; - return validate(expectedProperties, res.job); + 'id', + 'state', + 'creationTimeStamp', + 'jobConditionCode' + ] + return validate(expectedProperties, res.job) } }, { - title: "Execute Script Viya - complete job", - description: "Should execute sas file and return log", + title: 'Execute Script Viya - complete job', + description: 'Should execute sas file and return log', test: () => { - const fileLines = [ - `data;`, - `do x=1 to 100;`, - `output;`, - `end;`, - `run;` - ]; + const fileLines = [`data;`, `do x=1 to 100;`, `output;`, `end;`, `run;`] return adapter.executeScriptSASViya( - "sasCode.sas", + 'sasCode.sas', fileLines, - "SAS Studio compute context", + 'SAS Studio compute context', undefined, true - ); + ) }, assertion: (res: any) => { - const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`; + const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n` - return validateLog(expectedLogContent, res.log); + return validateLog(expectedLogContent, res.log) } }, { - title: "Execute Script Viya - failed job", - description: "Should execute sas file and return log", + title: 'Execute Script Viya - failed job', + description: 'Should execute sas file and return log', test: () => { - const fileLines = [`%abort;`]; + const fileLines = [`%abort;`] return adapter .executeScriptSASViya( - "sasCode.sas", + 'sasCode.sas', fileLines, - "SAS Studio compute context", + 'SAS Studio compute context', undefined, true ) - .catch((err: any) => err); + .catch((err: any) => err) }, assertion: (res: any) => { - const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`; + const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n` - return validateLog(expectedLogContent, res.log); + return validateLog(expectedLogContent, res.log) } } ] -}); +}) const validateLog = (text: string, log: string): boolean => { - const isValid = JSON.stringify(log).includes(text); + const isValid = JSON.stringify(log).includes(text) - return isValid; -}; + return isValid +} const validate = (expectedProperties: string[], data: any): boolean => { - const actualProperties = Object.keys(data); + const actualProperties = Object.keys(data) const isValid = expectedProperties.every((property) => actualProperties.includes(property) - ); - return isValid; -}; + ) + return isValid +} diff --git a/sasjs-tests/src/testSuites/RequestData.ts b/sasjs-tests/src/testSuites/RequestData.ts index 11985b7..a2088f3 100644 --- a/sasjs-tests/src/testSuites/RequestData.ts +++ b/sasjs-tests/src/testSuites/RequestData.ts @@ -1,112 +1,112 @@ -import SASjs from "@sasjs/adapter"; -import { TestSuite } from "@sasjs/test-framework"; +import SASjs from '@sasjs/adapter' +import { TestSuite } from '@sasjs/test-framework' -const stringData: any = { table1: [{ col1: "first col value" }] }; -const numericData: any = { table1: [{ col1: 3.14159265 }] }; +const stringData: any = { table1: [{ col1: 'first col value' }] } +const numericData: any = { table1: [{ col1: 3.14159265 }] } const multiColumnData: any = { - table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }] -}; + table1: [{ col1: 42, col2: 1.618, col3: 'x', col4: 'x' }] +} const multipleRowsWithNulls: any = { table1: [ - { col1: 42, col2: null, col3: "x", col4: "" }, - { col1: 42, col2: null, col3: "x", col4: "" }, - { col1: 42, col2: null, col3: "x", col4: "" }, - { col1: 42, col2: 1.62, col3: "x", col4: "x" }, - { col1: 42, col2: 1.62, col3: "x", col4: "x" } + { col1: 42, col2: null, col3: 'x', col4: '' }, + { col1: 42, col2: null, col3: 'x', col4: '' }, + { col1: 42, col2: null, col3: 'x', col4: '' }, + { col1: 42, col2: 1.62, col3: 'x', col4: 'x' }, + { col1: 42, col2: 1.62, col3: 'x', col4: 'x' } ] -}; +} const multipleColumnsWithNulls: any = { table1: [ - { col1: 42, col2: null, col3: "x", col4: null }, - { col1: 42, col2: null, col3: "x", col4: null }, - { col1: 42, col2: null, col3: "x", col4: null }, - { col1: 42, col2: null, col3: "x", col4: "" }, - { col1: 42, col2: null, col3: "x", col4: "" } + { col1: 42, col2: null, col3: 'x', col4: null }, + { col1: 42, col2: null, col3: 'x', col4: null }, + { col1: 42, col2: null, col3: 'x', col4: null }, + { col1: 42, col2: null, col3: 'x', col4: '' }, + { col1: 42, col2: null, col3: 'x', col4: '' } ] -}; +} const getLongStringData = (length = 32764) => { - let x = "X"; + let x = 'X' for (let i = 1; i <= length; i++) { - x = x + "X"; + x = x + 'X' } - const data: any = { table1: [{ col1: x }] }; - return data; -}; + const data: any = { table1: [{ col1: x }] } + return data +} const getLargeObjectData = () => { - const data = { table1: [{ big: "data" }] }; + const data = { table1: [{ big: 'data' }] } for (let i = 1; i < 10000; i++) { - data.table1.push(data.table1[0]); + data.table1.push(data.table1[0]) } - return data; -}; + return data +} export const sendArrTests = (adapter: SASjs): TestSuite => ({ - name: "sendArr", + name: 'sendArr', tests: [ { - title: "Absolute paths", - description: "Should work with absolute paths to SAS jobs", + title: 'Absolute paths', + description: 'Should work with absolute paths to SAS jobs', test: () => { - return adapter.request("/Public/app/common/sendArr", stringData); + return adapter.request('/Public/app/common/sendArr', stringData) }, assertion: (res: any) => { - return res.table1[0][0] === stringData.table1[0].col1; + return res.table1[0][0] === stringData.table1[0].col1 } }, { - title: "Single string value", - description: "Should send an array with a single string value", + title: 'Single string value', + description: 'Should send an array with a single string value', test: () => { - return adapter.request("common/sendArr", stringData); + return adapter.request('common/sendArr', stringData) }, assertion: (res: any) => { - return res.table1[0][0] === stringData.table1[0].col1; + return res.table1[0][0] === stringData.table1[0].col1 } }, { - title: "Long string value", + title: 'Long string value', description: - "Should send an array with a long string value under 32765 characters", + 'Should send an array with a long string value under 32765 characters', test: () => { - return adapter.request("common/sendArr", getLongStringData()); + return adapter.request('common/sendArr', getLongStringData()) }, assertion: (res: any) => { - const longStringData = getLongStringData(); - return res.table1[0][0] === longStringData.table1[0].col1; + const longStringData = getLongStringData() + return res.table1[0][0] === longStringData.table1[0].col1 } }, { - title: "Overly long string value", + title: 'Overly long string value', description: - "Should error out with long string values over 32765 characters", + 'Should error out with long string values over 32765 characters', test: () => { - const data = getLongStringData(32767); - return adapter.request("common/sendArr", data).catch((e) => e); + const data = getLongStringData(32767) + return adapter.request('common/sendArr', data).catch((e) => e) }, assertion: (error: any) => { - return !!error && !!error.error && !!error.error.message; + return !!error && !!error.error && !!error.error.message } }, { - title: "Single numeric value", - description: "Should send an array with a single numeric value", + title: 'Single numeric value', + description: 'Should send an array with a single numeric value', test: () => { - return adapter.request("common/sendArr", numericData); + return adapter.request('common/sendArr', numericData) }, assertion: (res: any) => { - return res.table1[0][0] === numericData.table1[0].col1; + return res.table1[0][0] === numericData.table1[0].col1 } }, { - title: "Multiple columns", - description: "Should handle data with multiple columns", + title: 'Multiple columns', + description: 'Should handle data with multiple columns', test: () => { - return adapter.request("common/sendArr", multiColumnData); + return adapter.request('common/sendArr', multiColumnData) }, assertion: (res: any) => { return ( @@ -114,144 +114,141 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({ res.table1[0][1] === multiColumnData.table1[0].col2 && res.table1[0][2] === multiColumnData.table1[0].col3 && res.table1[0][3] === multiColumnData.table1[0].col4 - ); + ) } }, { - title: "Multiple rows with nulls", - description: "Should handle data with multiple rows with null values", + title: 'Multiple rows with nulls', + description: 'Should handle data with multiple rows with null values', test: () => { - return adapter.request("common/sendArr", multipleRowsWithNulls); + return adapter.request('common/sendArr', multipleRowsWithNulls) }, assertion: (res: any) => { - let result = true; + let result = true multipleRowsWithNulls.table1.forEach((_: any, index: number) => { result = result && - res.table1[index][0] === multipleRowsWithNulls.table1[index].col1; + res.table1[index][0] === multipleRowsWithNulls.table1[index].col1 result = result && - res.table1[index][1] === multipleRowsWithNulls.table1[index].col2; + res.table1[index][1] === multipleRowsWithNulls.table1[index].col2 result = result && - res.table1[index][2] === multipleRowsWithNulls.table1[index].col3; + res.table1[index][2] === multipleRowsWithNulls.table1[index].col3 result = result && res.table1[index][3] === - (multipleRowsWithNulls.table1[index].col4 || " "); - }); - return result; + (multipleRowsWithNulls.table1[index].col4 || ' ') + }) + return result } }, { - title: "Multiple columns with nulls", - description: "Should handle data with multiple columns with null values", + title: 'Multiple columns with nulls', + description: 'Should handle data with multiple columns with null values', test: () => { - return adapter.request("common/sendArr", multipleColumnsWithNulls); + return adapter.request('common/sendArr', multipleColumnsWithNulls) }, assertion: (res: any) => { - let result = true; + let result = true multipleColumnsWithNulls.table1.forEach((_: any, index: number) => { result = result && - res.table1[index][0] === - multipleColumnsWithNulls.table1[index].col1; + res.table1[index][0] === multipleColumnsWithNulls.table1[index].col1 result = result && - res.table1[index][1] === - multipleColumnsWithNulls.table1[index].col2; + res.table1[index][1] === multipleColumnsWithNulls.table1[index].col2 result = result && - res.table1[index][2] === - multipleColumnsWithNulls.table1[index].col3; + res.table1[index][2] === multipleColumnsWithNulls.table1[index].col3 result = result && res.table1[index][3] === - (multipleColumnsWithNulls.table1[index].col4 || " "); - }); - return result; + (multipleColumnsWithNulls.table1[index].col4 || ' ') + }) + return result } } ] -}); +}) export const sendObjTests = (adapter: SASjs): TestSuite => ({ - name: "sendObj", + name: 'sendObj', tests: [ { - title: "Invalid column name", - description: "Should throw an error", + title: 'Invalid column name', + description: 'Should throw an error', test: async () => { const invalidData: any = { - "1 invalid table": [{ col1: 42 }] - }; - return adapter.request("common/sendObj", invalidData).catch((e) => e); + '1 invalid table': [{ col1: 42 }] + } + return adapter.request('common/sendObj', invalidData).catch((e) => e) }, assertion: (error: any) => !!error && !!error.error && !!error.error.message }, { - title: "Single string value", - description: "Should send an object with a single string value", + title: 'Single string value', + description: 'Should send an object with a single string value', test: () => { - return adapter.request("common/sendObj", stringData); + return adapter.request('common/sendObj', stringData) }, assertion: (res: any) => { - return res.table1[0].COL1 === stringData.table1[0].col1; + return res.table1[0].COL1 === stringData.table1[0].col1 } }, { - title: "Long string value", + title: 'Long string value', description: - "Should send an object with a long string value under 32765 characters", + 'Should send an object with a long string value under 32765 characters', test: () => { - return adapter.request("common/sendObj", getLongStringData()); + return adapter.request('common/sendObj', getLongStringData()) }, assertion: (res: any) => { - const longStringData = getLongStringData(); - return res.table1[0].COL1 === longStringData.table1[0].col1; + const longStringData = getLongStringData() + return res.table1[0].COL1 === longStringData.table1[0].col1 } }, { - title: "Overly long string value", + title: 'Overly long string value', description: - "Should error out with long string values over 32765 characters", + 'Should error out with long string values over 32765 characters', test: () => { return adapter - .request("common/sendObj", getLongStringData(32767)) - .catch((e) => e); + .request('common/sendObj', getLongStringData(32767)) + .catch((e) => e) }, assertion: (error: any) => { - return !!error && !!error.error && !!error.error.message; + return !!error && !!error.error && !!error.error.message } }, { - title: "Single numeric value", - description: "Should send an object with a single numeric value", + title: 'Single numeric value', + description: 'Should send an object with a single numeric value', test: () => { - return adapter.request("common/sendObj", numericData); + return adapter.request('common/sendObj', numericData) }, assertion: (res: any) => { - return res.table1[0].COL1 === numericData.table1[0].col1; + return res.table1[0].COL1 === numericData.table1[0].col1 } }, { - title: "Large data volume", - description: "Should send an object with a large amount of data", + title: 'Large data volume', + description: 'Should send an object with a large amount of data', test: () => { - return adapter.request("common/sendObj", getLargeObjectData()); + return adapter.request('common/sendObj', getLargeObjectData()) }, assertion: (res: any) => { - const data = getLargeObjectData(); - return res.table1[9000].BIG === data.table1[9000].big; + const data = getLargeObjectData() + return res.table1[9000].BIG === data.table1[9000].big } }, { - title: "Multiple columns", - description: "Should handle data with multiple columns", + title: 'Multiple columns', + description: 'Should handle data with multiple columns', test: () => { - return adapter.request("common/sendObj", multiColumnData); + return adapter.request('common/sendObj', multiColumnData) }, assertion: (res: any) => { return ( @@ -259,63 +256,63 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({ res.table1[0].COL2 === multiColumnData.table1[0].col2 && res.table1[0].COL3 === multiColumnData.table1[0].col3 && res.table1[0].COL4 === multiColumnData.table1[0].col4 - ); + ) } }, { - title: "Multiple rows with nulls", - description: "Should handle data with multiple rows with null values", + title: 'Multiple rows with nulls', + description: 'Should handle data with multiple rows with null values', test: () => { - return adapter.request("common/sendObj", multipleRowsWithNulls); + return adapter.request('common/sendObj', multipleRowsWithNulls) }, assertion: (res: any) => { - let result = true; + let result = true multipleRowsWithNulls.table1.forEach((_: any, index: number) => { result = result && - res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1; + res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1 result = result && - res.table1[index].COL2 === multipleRowsWithNulls.table1[index].col2; + res.table1[index].COL2 === multipleRowsWithNulls.table1[index].col2 result = result && - res.table1[index].COL3 === multipleRowsWithNulls.table1[index].col3; + res.table1[index].COL3 === multipleRowsWithNulls.table1[index].col3 result = result && res.table1[index].COL4 === - (multipleRowsWithNulls.table1[index].col4 || " "); - }); - return result; + (multipleRowsWithNulls.table1[index].col4 || ' ') + }) + return result } }, { - title: "Multiple columns with nulls", - description: "Should handle data with multiple columns with null values", + title: 'Multiple columns with nulls', + description: 'Should handle data with multiple columns with null values', test: () => { - return adapter.request("common/sendObj", multipleColumnsWithNulls); + return adapter.request('common/sendObj', multipleColumnsWithNulls) }, assertion: (res: any) => { - let result = true; + let result = true multipleColumnsWithNulls.table1.forEach((_: any, index: number) => { result = result && res.table1[index].COL1 === - multipleColumnsWithNulls.table1[index].col1; + multipleColumnsWithNulls.table1[index].col1 result = result && res.table1[index].COL2 === - multipleColumnsWithNulls.table1[index].col2; + multipleColumnsWithNulls.table1[index].col2 result = result && res.table1[index].COL3 === - multipleColumnsWithNulls.table1[index].col3; + multipleColumnsWithNulls.table1[index].col3 result = result && res.table1[index].COL4 === - (multipleColumnsWithNulls.table1[index].col4 || " "); - }); - return result; + (multipleColumnsWithNulls.table1[index].col4 || ' ') + }) + return result } } ] -}); +}) diff --git a/sasjs-tests/src/testSuites/SasjsRequests.ts b/sasjs-tests/src/testSuites/SasjsRequests.ts index 2f0b75d..baa131b 100644 --- a/sasjs-tests/src/testSuites/SasjsRequests.ts +++ b/sasjs-tests/src/testSuites/SasjsRequests.ts @@ -1,49 +1,49 @@ -import SASjs from "@sasjs/adapter"; -import { TestSuite } from "@sasjs/test-framework"; +import SASjs from '@sasjs/adapter' +import { TestSuite } from '@sasjs/test-framework' -const data: any = { table1: [{ col1: "first col value" }] }; +const data: any = { table1: [{ col1: 'first col value' }] } export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({ - name: "SASjs Requests", + name: 'SASjs Requests', tests: [ { - title: "WORK tables", - description: "Should get WORK tables after request", + title: 'WORK tables', + description: 'Should get WORK tables after request', test: async () => { - return adapter.request("common/sendArr", data); + return adapter.request('common/sendArr', data) }, assertion: () => { - const requests = adapter.getSasRequests(); + const requests = adapter.getSasRequests() if (adapter.getSasjsConfig().debug) { - return requests[0].SASWORK !== null; + return requests[0].SASWORK !== null } else { - return requests[0].SASWORK === null; + return requests[0].SASWORK === null } } }, { - title: "Make error and capture log", + title: 'Make error and capture log', description: - "Should make an error and capture log, in the same time it is testing if debug override is working", + 'Should make an error and capture log, in the same time it is testing if debug override is working', test: async () => { return adapter - .request("common/makeErr", data, { debug: true }) + .request('common/makeErr', data, { debug: true }) .catch(() => { - const sasRequests = adapter.getSasRequests(); + const sasRequests = adapter.getSasRequests() const makeErrRequest: any = - sasRequests.find((req) => req.serviceLink.includes("makeErr")) || - null; + sasRequests.find((req) => req.serviceLink.includes('makeErr')) || + null - if (!makeErrRequest) return false; + if (!makeErrRequest) return false return !!( makeErrRequest.logFile && makeErrRequest.logFile.length > 0 - ); - }); + ) + }) }, assertion: (response) => { - return response; + return response } } ] -}); +}) diff --git a/sasjs-tests/src/testSuites/SpecialCases.ts b/sasjs-tests/src/testSuites/SpecialCases.ts index 4121044..1ec227a 100644 --- a/sasjs-tests/src/testSuites/SpecialCases.ts +++ b/sasjs-tests/src/testSuites/SpecialCases.ts @@ -1,91 +1,92 @@ -import SASjs from "@sasjs/adapter"; -import { TestSuite } from "@sasjs/test-framework"; +import SASjs from '@sasjs/adapter' +import { TestSuite } from '@sasjs/test-framework' const specialCharData: any = { table1: [ { - tab: "\t", - lf: "\n", - cr: "\r", - semicolon: ";semi", - percent: "%", + tab: '\t', + lf: '\n', + cr: '\r', + semicolon: ';semi', + percent: '%', singleQuote: "'", doubleQuote: '"', - crlf: "\r\n", - euro: "€euro", - banghash: "!#banghash" + crlf: '\r\n', + euro: '€euro', + banghash: '!#banghash', + dot: '.' } ] -}; +} const moreSpecialCharData: any = { table1: [ { speech0: '"speech', - pct: "%percent", + pct: '%percent', speech: '"speech', - slash: "\\slash", - slashWithSpecial: "\\\tslash", - macvar: "&sysuserid", - chinese: "传/傳chinese", - sigma: "Σsigma", - at: "@at", - serbian: "Српски", - dollar: "$" + slash: '\\slash', + slashWithSpecial: '\\\tslash', + macvar: '&sysuserid', + chinese: '传/傳chinese', + sigma: 'Σsigma', + at: '@at', + serbian: 'Српски', + dollar: '$' } ] -}; +} const getWideData = () => { - const cols: any = {}; + const cols: any = {} for (let i = 1; i <= 10000; i++) { - cols["col" + i] = "test" + i; + cols['col' + i] = 'test' + i } const data: any = { table1: [cols] - }; + } - return data; -}; + return data +} const getTables = () => { - const tables: any = {}; + const tables: any = {} for (let i = 1; i <= 100; i++) { - tables["table" + i] = [{ col1: "x", col2: "x", col3: "x", col4: "x" }]; + tables['table' + i] = [{ col1: 'x', col2: 'x', col3: 'x', col4: 'x' }] } - return tables; -}; + return tables +} const getLargeDataset = () => { - const rows: any = []; + const rows: any = [] const colData: string = - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' for (let i = 1; i <= 10000; i++) { - rows.push({ col1: colData, col2: colData, col3: colData, col4: colData }); + rows.push({ col1: colData, col2: colData, col3: colData, col4: colData }) } const data: any = { table1: rows - }; + } - return data; -}; + return data +} const errorAndCsrfData: any = { - error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }], - _csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }] -}; + error: [{ col1: 'q', col2: 'w', col3: 'e', col4: 'r' }], + _csrf: [{ col1: 'q', col2: 'w', col3: 'e', col4: 'r' }] +} export const specialCaseTests = (adapter: SASjs): TestSuite => ({ - name: "Special Cases", + name: 'Special Cases', tests: [ { - title: "Common special characters", - description: "Should handle common special characters", + title: 'Common special characters', + description: 'Should handle common special characters', test: () => { - return adapter.request("common/sendArr", specialCharData); + return adapter.request('common/sendArr', specialCharData) }, assertion: (res: any) => { return ( @@ -96,17 +97,18 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({ res.table1[0][4] === specialCharData.table1[0].percent && res.table1[0][5] === specialCharData.table1[0].singleQuote && res.table1[0][6] === specialCharData.table1[0].doubleQuote && - res.table1[0][7] === "\n" && + res.table1[0][7] === '\n' && res.table1[0][8] === specialCharData.table1[0].euro && - res.table1[0][9] === specialCharData.table1[0].banghash - ); + res.table1[0][9] === specialCharData.table1[0].banghash && + res.table1[0][10] === specialCharData.table1[0].dot + ) } }, { - title: "Other special characters", - description: "Should handle other special characters", + title: 'Other special characters', + description: 'Should handle other special characters', test: () => { - return adapter.request("common/sendArr", moreSpecialCharData); + return adapter.request('common/sendArr', moreSpecialCharData) }, assertion: (res: any) => { return ( @@ -121,50 +123,50 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({ res.table1[0][8] === moreSpecialCharData.table1[0].at && res.table1[0][9] === moreSpecialCharData.table1[0].serbian && res.table1[0][10] === moreSpecialCharData.table1[0].dollar - ); + ) } }, { - title: "Wide table with sendArr", - description: "Should handle data with 10000 columns", + title: 'Wide table with sendArr', + description: 'Should handle data with 10000 columns', test: () => { - return adapter.request("common/sendArr", getWideData()); + return adapter.request('common/sendArr', getWideData()) }, assertion: (res: any) => { - const data = getWideData(); - let result = true; + const data = getWideData() + let result = true for (let i = 0; i <= 10; i++) { result = - result && res.table1[0][i] === data.table1[0]["col" + (i + 1)]; + result && res.table1[0][i] === data.table1[0]['col' + (i + 1)] } - return result; + return result } }, { - title: "Wide table with sendObj", - description: "Should handle data with 10000 columns", + title: 'Wide table with sendObj', + description: 'Should handle data with 10000 columns', test: () => { - return adapter.request("common/sendObj", getWideData()); + return adapter.request('common/sendObj', getWideData()) }, assertion: (res: any) => { - const data = getWideData(); - let result = true; + const data = getWideData() + let result = true for (let i = 0; i <= 10; i++) { result = result && - res.table1[0]["COL" + (i + 1)] === data.table1[0]["col" + (i + 1)]; + res.table1[0]['COL' + (i + 1)] === data.table1[0]['col' + (i + 1)] } - return result; + return result } }, { - title: "Multiple tables", - description: "Should handle data with 100 tables", + title: 'Multiple tables', + description: 'Should handle data with 100 tables', test: () => { - return adapter.request("common/sendArr", getTables()); + return adapter.request('common/sendArr', getTables()) }, assertion: (res: any) => { - const data = getTables(); + const data = getTables() return ( res.table1[0][0] === data.table1[0].col1 && res.table1[0][1] === data.table1[0].col2 && @@ -174,45 +176,45 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({ res.table50[0][1] === data.table50[0].col2 && res.table50[0][2] === data.table50[0].col3 && res.table50[0][3] === data.table50[0].col4 - ); + ) } }, { - title: "Large dataset with sendObj", - description: "Should handle 5mb of data", + title: 'Large dataset with sendObj', + description: 'Should handle 5mb of data', test: () => { - return adapter.request("common/sendObj", getLargeDataset()); + return adapter.request('common/sendObj', getLargeDataset()) }, assertion: (res: any) => { - const data = getLargeDataset(); - let result = true; + const data = getLargeDataset() + let result = true for (let i = 0; i <= 10; i++) { - result = result && res.table1[i][0] === data.table1[i][0]; + result = result && res.table1[i][0] === data.table1[i][0] } - return result; + return result } }, { - title: "Large dataset with sendArr", - description: "Should handle 5mb of data", + title: 'Large dataset with sendArr', + description: 'Should handle 5mb of data', test: () => { - return adapter.request("common/sendArr", getLargeDataset()); + return adapter.request('common/sendArr', getLargeDataset()) }, assertion: (res: any) => { - const data = getLargeDataset(); - let result = true; + const data = getLargeDataset() + let result = true for (let i = 0; i <= 10; i++) { result = - result && res.table1[i][0] === Object.values(data.table1[i])[0]; + result && res.table1[i][0] === Object.values(data.table1[i])[0] } - return result; + return result } }, { - title: "Error and _csrf tables with sendArr", - description: "Should handle error and _csrf tables", + title: 'Error and _csrf tables with sendArr', + description: 'Should handle error and _csrf tables', test: () => { - return adapter.request("common/sendArr", errorAndCsrfData); + return adapter.request('common/sendArr', errorAndCsrfData) }, assertion: (res: any) => { return ( @@ -224,14 +226,14 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({ res._csrf[0][1] === errorAndCsrfData._csrf[0].col2 && res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 && res._csrf[0][3] === errorAndCsrfData._csrf[0].col4 - ); + ) } }, { - title: "Error and _csrf tables with sendObj", - description: "Should handle error and _csrf tables", + title: 'Error and _csrf tables with sendObj', + description: 'Should handle error and _csrf tables', test: () => { - return adapter.request("common/sendObj", errorAndCsrfData); + return adapter.request('common/sendObj', errorAndCsrfData) }, assertion: (res: any) => { return ( @@ -243,8 +245,8 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({ res._csrf[0].COL2 === errorAndCsrfData._csrf[0].col2 && res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 && res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4 - ); + ) } } ] -}); +}) diff --git a/sasjs-tests/src/utils/Assert.ts b/sasjs-tests/src/utils/Assert.ts index 9a04a7d..ec6850c 100644 --- a/sasjs-tests/src/utils/Assert.ts +++ b/sasjs-tests/src/utils/Assert.ts @@ -1,22 +1,22 @@ export const assert = ( expression: boolean | (() => boolean), - message = "Assertion failed" + message = 'Assertion failed' ) => { - let result; + let result try { - if (typeof expression === "boolean") { - result = expression; + if (typeof expression === 'boolean') { + result = expression } else { - result = expression(); + result = expression() } } catch (e) { - console.error(message); - throw new Error(message); + console.error(message) + throw new Error(message) } if (!!result) { - return; + return } else { - console.error(message); - throw new Error(message); + console.error(message) + throw new Error(message) } -}; +} diff --git a/src/utils/convertToCsv.ts b/src/utils/convertToCsv.ts index 8260a93..9496514 100644 --- a/src/utils/convertToCsv.ts +++ b/src/utils/convertToCsv.ts @@ -53,7 +53,7 @@ export const convertToCSV = (data: any) => { ) } - return `${field}:${firstFoundType === 'chars' ? '$' : ''}${ + return `${field}:${firstFoundType === 'chars' ? '$char' : ''}${ longestValueForField ? longestValueForField : firstFoundType === 'chars' From 99e192c5de68d3c789dc636d085ad58702462e40 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Fri, 7 May 2021 10:36:43 +0300 Subject: [PATCH 7/7] test(csv-convert): fixed expected output --- src/utils/convertToCsv.spec.ts | 58 ++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/utils/convertToCsv.spec.ts b/src/utils/convertToCsv.spec.ts index b6e48bd..8a56335 100644 --- a/src/utils/convertToCsv.spec.ts +++ b/src/utils/convertToCsv.spec.ts @@ -8,7 +8,7 @@ describe('convertToCsv', () => { { foo: 'asd', bar: `'qwert'` } ] - const expectedOutput = `foo:$5. bar:$7.\r\n"'bar'",abc\r\nsadf,def\r\nasd,"'qwert'"` + const expectedOutput = `foo:$char5. bar:$char7.\r\n"'bar'",abc\r\nsadf,def\r\nasd,"'qwert'"` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -20,7 +20,7 @@ describe('convertToCsv', () => { { foo: 'asd', bar: `"qwert"` } ] - const expectedOutput = `foo:$5. bar:$7.\r\n"""bar""",abc\r\nsadf,def\r\nasd,"""qwert"""` + const expectedOutput = `foo:$char5. bar:$char7.\r\n"""bar""",abc\r\nsadf,def\r\nasd,"""qwert"""` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -28,7 +28,7 @@ describe('convertToCsv', () => { it('should convert values with mixed quotes', () => { const data = [{ foo: `'blah'`, bar: `"blah"` }] - const expectedOutput = `foo:$6. bar:$6.\r\n"'blah'","""blah"""` + const expectedOutput = `foo:$char6. bar:$char6.\r\n"'blah'","""blah"""` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -36,7 +36,7 @@ describe('convertToCsv', () => { it('should convert values with mixed quotes', () => { const data = [{ foo: `'blah,"'`, bar: `"blah,blah" "` }] - const expectedOutput = `foo:$8. bar:$13.\r\n"'blah,""'","""blah,blah"" """` + const expectedOutput = `foo:$char8. bar:$char13.\r\n"'blah,""'","""blah,blah"" """` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -44,7 +44,7 @@ describe('convertToCsv', () => { it('should convert values with mixed quotes', () => { const data = [{ foo: `',''`, bar: `","` }] - const expectedOutput = `foo:$4. bar:$3.\r\n"',''",""","""` + const expectedOutput = `foo:$char4. bar:$char3.\r\n"',''",""","""` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -52,7 +52,7 @@ describe('convertToCsv', () => { it('should convert values with mixed quotes', () => { const data = [{ foo: `','`, bar: `,"` }] - const expectedOutput = `foo:$3. bar:$2.\r\n"','",","""` + const expectedOutput = `foo:$char3. bar:$char2.\r\n"','",","""` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -60,7 +60,7 @@ describe('convertToCsv', () => { it('should convert values with mixed quotes', () => { const data = [{ foo: `"`, bar: `'` }] - const expectedOutput = `foo:$1. bar:$1.\r\n"""","'"` + const expectedOutput = `foo:$char1. bar:$char1.\r\n"""","'"` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -68,7 +68,7 @@ describe('convertToCsv', () => { it('should convert values with mixed quotes', () => { const data = [{ foo: `,`, bar: `',` }] - const expectedOutput = `foo:$1. bar:$2.\r\n",","',"` + const expectedOutput = `foo:$char1. bar:$char2.\r\n",","',"` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -82,7 +82,7 @@ describe('convertToCsv', () => { { col1: 42, col2: null, col3: 'x', col4: '' } ] - const expectedOutput = `col1:best. col2:best. col3:$1. col4:$1.\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,` + const expectedOutput = `col1:best. col2:best. col3:$char1. col4:$char1.\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,` expect(convertToCSV(data)).toEqual(expectedOutput) }) @@ -96,28 +96,28 @@ describe('convertToCsv', () => { { col1: 42, col2: 1.62, col3: 'x', col4: 'x' } ] - const expectedOutput = `col1:best. col2:best. col3:$1. col4:$1.\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,1.62,x,x\r\n42,1.62,x,x` + const expectedOutput = `col1:best. col2:best. col3:$char1. col4:$char1.\r\n42,.,x,\r\n42,.,x,\r\n42,.,x,\r\n42,1.62,x,x\r\n42,1.62,x,x` expect(convertToCSV(data)).toEqual(expectedOutput) }) it('should convert values with common special characters', () => { - expect(convertToCSV([{ tab: '\t' }])).toEqual(`tab:$1.\r\n\"\t\"`) - expect(convertToCSV([{ lf: '\n' }])).toEqual(`lf:$1.\r\n\"\n\"`) + expect(convertToCSV([{ tab: '\t' }])).toEqual(`tab:$char1.\r\n\"\t\"`) + expect(convertToCSV([{ lf: '\n' }])).toEqual(`lf:$char1.\r\n\"\n\"`) expect(convertToCSV([{ semicolon: ';semi' }])).toEqual( - `semicolon:$5.\r\n;semi` + `semicolon:$char5.\r\n;semi` ) - expect(convertToCSV([{ percent: '%' }])).toEqual(`percent:$1.\r\n%`) + expect(convertToCSV([{ percent: '%' }])).toEqual(`percent:$char1.\r\n%`) expect(convertToCSV([{ singleQuote: "'" }])).toEqual( - `singleQuote:$1.\r\n\"'\"` + `singleQuote:$char1.\r\n\"'\"` ) expect(convertToCSV([{ doubleQuote: '"' }])).toEqual( - `doubleQuote:$1.\r\n""""` + `doubleQuote:$char1.\r\n""""` ) - expect(convertToCSV([{ crlf: '\r\n' }])).toEqual(`crlf:$2.\r\n\"\n\"`) - expect(convertToCSV([{ euro: '€euro' }])).toEqual(`euro:$7.\r\n€euro`) + expect(convertToCSV([{ crlf: '\r\n' }])).toEqual(`crlf:$char2.\r\n\"\n\"`) + expect(convertToCSV([{ euro: '€euro' }])).toEqual(`euro:$char7.\r\n€euro`) expect(convertToCSV([{ banghash: '!#banghash' }])).toEqual( - `banghash:$10.\r\n!#banghash` + `banghash:$char10.\r\n!#banghash` ) }) @@ -138,31 +138,33 @@ describe('convertToCsv', () => { } ] - const expectedOutput = `speech0:$7. pct:$8. speech:$7. slash:$6. slashWithSpecial:$7. macvar:$10. chinese:$14. sigma:$7. at:$3. serbian:$12. dollar:$1.\r\n"""speech",%percent,"""speech",\\slash,\"\\\tslash\",&sysuserid,传/傳chinese,Σsigma,@at,Српски,$` + const expectedOutput = `speech0:$char7. pct:$char8. speech:$char7. slash:$char6. slashWithSpecial:$char7. macvar:$char10. chinese:$char14. sigma:$char7. at:$char3. serbian:$char12. dollar:$char1.\r\n"""speech",%percent,"""speech",\\slash,\"\\\tslash\",&sysuserid,传/傳chinese,Σsigma,@at,Српски,$` expect(convertToCSV(data)).toEqual(expectedOutput) - expect(convertToCSV([{ speech: 'menext' }])).toEqual(`speech:$6.\r\nmenext`) + expect(convertToCSV([{ speech: 'menext' }])).toEqual( + `speech:$char6.\r\nmenext` + ) expect(convertToCSV([{ speech: 'me\nnext' }])).toEqual( - `speech:$7.\r\n\"me\nnext\"` + `speech:$char7.\r\n\"me\nnext\"` ) expect(convertToCSV([{ speech: `me'next` }])).toEqual( - `speech:$7.\r\n\"me'next\"` + `speech:$char7.\r\n\"me'next\"` ) expect(convertToCSV([{ speech: `me"next` }])).toEqual( - `speech:$7.\r\n\"me""next\"` + `speech:$char7.\r\n\"me""next\"` ) expect(convertToCSV([{ speech: `me""next` }])).toEqual( - `speech:$8.\r\n\"me""""next\"` + `speech:$char8.\r\n\"me""""next\"` ) expect(convertToCSV([{ slashWithSpecial: '\\\tslash' }])).toEqual( - `slashWithSpecial:$7.\r\n\"\\\tslash\"` + `slashWithSpecial:$char7.\r\n\"\\\tslash\"` ) expect(convertToCSV([{ slashWithSpecial: '\\ \tslash' }])).toEqual( - `slashWithSpecial:$8.\r\n\"\\ \tslash\"` + `slashWithSpecial:$char8.\r\n\"\\ \tslash\"` ) expect( convertToCSV([{ slashWithSpecialExtra: '\\\ts\tl\ta\ts\t\th\t' }]) - ).toEqual(`slashWithSpecialExtra:$13.\r\n\"\\\ts\tl\ta\ts\t\th\t\"`) + ).toEqual(`slashWithSpecialExtra:$char13.\r\n\"\\\ts\tl\ta\ts\t\th\t\"`) }) })