diff --git a/README.md b/README.md index 3aaa1428..d76f91e1 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,14 @@ Furthermore, it is possible to limit the result set using `hlx_p.limit` and page For more, see the [API documentation](docs/API.md). +## Working with Excel and Google Sheets + +- The sheet inside an Excel workbook or Google spreadsheet can be addressed using the `sheet` parameter. +- Only sheets having the `helix-` prefix can be addressed. +- If the workbook or spreadsheet does not have any `helix-` prefixed sheets, the first sheet is returned. +- By default, the _used range_ of the selected sheet is returned. +- For excel, A table can be addressed using the `table` request parameter, which can be a table name or an index. For example, `table=Table1` will return the table with the name `Table1`, `table=1` will return the second table in the sheet. + ## Development ### Deploying Helix Data Embed diff --git a/src/matchers/excel.js b/src/matchers/excel.js index 9cef1219..cae9cd57 100644 --- a/src/matchers/excel.js +++ b/src/matchers/excel.js @@ -16,6 +16,8 @@ async function extract(url, params, log = console) { AZURE_WORD2MD_CLIENT_ID: clientId, AZURE_HELIX_USER: username, AZURE_HELIX_PASSWORD: password, + sheet, + table, } = params; try { @@ -28,17 +30,54 @@ async function extract(url, params, log = console) { const item = await drive.getDriveItemFromShareLink(url); const workbook = drive.getWorkbook(item); - const worksheetNames = await workbook.getWorksheetNames(); - const worksheetName = worksheetNames[0]; - const worksheet = workbook.worksheet(worksheetName); - const tableNames = await worksheet.getTableNames(); + // if a sheet is specified, use it + let worksheetName; + if (sheet) { + worksheetName = `helix-${sheet}`; + } else { + // get the sheet names and check if any of them starts with `helix-` + const sheetNames = await workbook.getWorksheetNames(); + const hasHelixSheets = !!sheetNames.find((n) => n.startsWith('helix-')); + if (hasHelixSheets) { + if (sheetNames.indexOf('helix-default') < 0) { + // no helix-default -> not found + log.info('Workbook has helix sheets but no helix-default'); + return { + statusCode: 404, + headers: { + 'cache-control': 'no-store, private, must-revalidate', + }, + body: [], + }; + } + worksheetName = 'helix-default'; + } else { + [worksheetName] = sheetNames; + log.info(`Workbook has no helix sheets. using first one: ${worksheetName}`); + } + } + + const worksheet = workbook.worksheet(encodeURIComponent(worksheetName)); const body = await (async () => { - if (!tableNames.length) { - log.info(`worksheet ${worksheetName} has no tables, getting range instead`); + if (!table) { + log.info(`returning used-range for ${worksheetName}.`); return worksheet.usedRange().getRowsAsObjects(); } - log.info(`fetching table data for worksheet ${worksheetName} with name ${tableNames[0]}`); - return worksheet.table(tableNames[0]).getRowsAsObjects(); + // if table is a number, get the names + let tableName = table; + if (/^\d+$/.test(table)) { + const tableNum = Number.parseInt(table, 10); + const tableNames = await worksheet.getTableNames(); + if (tableNum < 0 || tableNum >= tableNames.length) { + log.info(`table index out of range: 0 >= ${tableNum} < ${tableNames.length}`); + const error = new Error('index out of range'); + error.statusCode = 404; + throw error; + } + tableName = tableNames[tableNum]; + } + log.info(`fetching table data for worksheet ${worksheetName} with name ${tableName}`); + return worksheet.table(encodeURIComponent(tableName)).getRowsAsObjects(); })(); log.info(`returning ${body.length} rows.`); return { diff --git a/src/matchers/google.js b/src/matchers/google.js index 03cc4ecb..4bebe19a 100644 --- a/src/matchers/google.js +++ b/src/matchers/google.js @@ -41,26 +41,47 @@ async function extract(url, params, log = console) { GOOGLE_DOCS2MD_CLIENT_ID: clientId, GOOGLE_DOCS2MD_CLIENT_SECRET: clientSecret, GOOGLE_DOCS2MD_REFRESH_TOKEN: refresh_token, + sheet, } = params; try { const spreadsheetId = getId(url); - const auth = createOAuthClient({ clientId, clientSecret }, { refresh_token }); - const sheets = google.sheets({ version: 'v4', auth }); - const { data } = await sheets.spreadsheets.get({ spreadsheetId, }); - const sheet1 = data.sheets[0].properties; - - const range = `${sheet1.title}!${new A1({ + let sheetTitle; + if (sheet) { + sheetTitle = `helix-${sheet}`; + } else { + const hasHelixSheets = !!data.sheets.find((s) => s.properties.title.startsWith('helix-')); + if (hasHelixSheets) { + sheetTitle = 'helix-default'; + } else { + sheetTitle = data.sheets[0].properties.title; + log.info(`Workbook has no helix sheets. using first one: ${sheetTitle}`); + } + } + + const sheet1 = data.sheets.find((s) => s.properties.title === sheetTitle); + if (!sheet1) { + log.info(`no sheet found with name ${sheetTitle}`); + return { + statusCode: 404, + headers: { + 'cache-control': 'no-store, private, must-revalidate', + }, + body: '', + }; + } + + const range = `${sheet1.properties.title}!${new A1({ colStart: 1, rowStart: 1, - nRows: sheet1.gridProperties.rowCount, - nCols: sheet1.gridProperties.columnCount, + nRows: sheet1.properties.gridProperties.rowCount, + nCols: sheet1.properties.gridProperties.columnCount, })}`; const values = await sheets.spreadsheets.values.get({ diff --git a/test/excel.integration.test.js b/test/excel.integration.test.js index 69acb232..d279e9d9 100644 --- a/test/excel.integration.test.js +++ b/test/excel.integration.test.js @@ -17,49 +17,118 @@ const { main } = require('../src/index'); require('dotenv').config(); +const condition = condit.hasenv('AZURE_WORD2MD_CLIENT_ID', 'AZURE_HELIX_USER', 'AZURE_HELIX_PASSWORD'); + +const DATA_COUNTRIES = [{ Country: 'Japan', Code: 'JP', Number: 3 }, + { Country: 'Germany', Code: 'DE', Number: 5 }, + { Country: 'USA', Code: 'US', Number: 7 }, + { Country: 'Switzerland', Code: 'CH', Number: 27 }, + { Country: 'France', Code: 'FR', Number: 99 }, + { Country: 'Australia', Code: 'AUS', Number: 12 }]; + describe('Excel Integration Test', () => { - condit('Retrieves Excel Spreadsheet without tables', condit.hasenv('AZURE_WORD2MD_CLIENT_ID', 'AZURE_HELIX_USER', 'AZURE_HELIX_PASSWORD'), async () => { + condit('Excel Spreadsheet without helix-default sheet returns 404', condition, async () => { + const result = await main({ + __ow_logger: console, + src: 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/data-embed-no-default.xlsx?d=wf80aa1d65efb4e41bd16ba3ca0a4564b&csf=1&web=1&e=9WnXzf', + AZURE_WORD2MD_CLIENT_ID: process.env.AZURE_WORD2MD_CLIENT_ID, + AZURE_HELIX_USER: process.env.AZURE_HELIX_USER, + AZURE_HELIX_PASSWORD: process.env.AZURE_HELIX_PASSWORD, + }); + assert.equal(result.statusCode, 404); + }).timeout(15000); + + condit('Excel Spreadsheet without helix-default but sheet params', condition, async () => { + const result = await main({ + __ow_logger: console, + src: 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/data-embed-no-default.xlsx?d=wf80aa1d65efb4e41bd16ba3ca0a4564b&csf=1&web=1&e=9WnXzf', + sheet: 'countries', + AZURE_WORD2MD_CLIENT_ID: process.env.AZURE_WORD2MD_CLIENT_ID, + AZURE_HELIX_USER: process.env.AZURE_HELIX_USER, + AZURE_HELIX_PASSWORD: process.env.AZURE_HELIX_PASSWORD, + }); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, DATA_COUNTRIES); + }).timeout(15000); + + condit('Excel Spreadsheet without helix returns first sheet', condition, async () => { + const result = await main({ + __ow_logger: console, + src: 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/data-embed-no-helix.xlsx?d=w88ace0003b6847a48b6bb84b79a5b72b&csf=1&web=1&e=Sg8lKt', + AZURE_WORD2MD_CLIENT_ID: process.env.AZURE_WORD2MD_CLIENT_ID, + AZURE_HELIX_USER: process.env.AZURE_HELIX_USER, + AZURE_HELIX_PASSWORD: process.env.AZURE_HELIX_PASSWORD, + }); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, [{ Country: 'Japan', Code: 'JP', Number: 81 }]); + }).timeout(15000); + + condit('Excel Spreadsheet without helix-default but sheet params Unicode', condition, async () => { + const result = await main({ + __ow_logger: console, + src: 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx?d=w6911fff4a52a4b3fb80560d8785adfa3&csf=1&web=1&e=fkkA2a', + sheet: '日本', + AZURE_WORD2MD_CLIENT_ID: process.env.AZURE_WORD2MD_CLIENT_ID, + AZURE_HELIX_USER: process.env.AZURE_HELIX_USER, + AZURE_HELIX_PASSWORD: process.env.AZURE_HELIX_PASSWORD, + }); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, [{ + Code: 'JP', + Country: 'Japan', + Number: 3, + }]); + }).timeout(15000); + + condit('Retrieves Excel Spreadsheet with helix-default (range)', condition, async () => { + const result = await main({ + __ow_logger: console, + src: 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx?d=w6911fff4a52a4b3fb80560d8785adfa3&csf=1&web=1&e=fkkA2a', + AZURE_WORD2MD_CLIENT_ID: process.env.AZURE_WORD2MD_CLIENT_ID, + AZURE_HELIX_USER: process.env.AZURE_HELIX_USER, + AZURE_HELIX_PASSWORD: process.env.AZURE_HELIX_PASSWORD, + }); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, DATA_COUNTRIES); + }).timeout(15000); + + condit('Retrieves Excel Spreadsheet with tables and table name', condition, async () => { const result = await main({ __ow_logger: console, - __ow_path: '/https://adobe-my.sharepoint.com/personal/trieloff_adobe_com/_layouts/15/guestaccess.aspx', - share: 'Edoi88tLKLpDsKzSfL-pcJYB2lIo7UKooYWnjm3w2WRrsA', - email: 'helix@adobe.com', - e: 'tD623x', + src: 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx?d=w6911fff4a52a4b3fb80560d8785adfa3&csf=1&web=1&e=fkkA2a', + sheet: 'tables', + table: 'Table1', AZURE_WORD2MD_CLIENT_ID: process.env.AZURE_WORD2MD_CLIENT_ID, AZURE_HELIX_USER: process.env.AZURE_HELIX_USER, AZURE_HELIX_PASSWORD: process.env.AZURE_HELIX_PASSWORD, }); assert.equal(result.statusCode, 200); - assert.equal(result.body.length, 3); + assert.deepEqual(result.body, [{ A: 112, B: 224, C: 135 }, { A: 2244, B: 234, C: 53 }]); }).timeout(15000); - condit('Retrieves Excel Spreadsheet with tables', condit.hasenv('AZURE_WORD2MD_CLIENT_ID', 'AZURE_HELIX_USER', 'AZURE_HELIX_PASSWORD'), async () => { + condit('Retrieves Excel Spreadsheet with tables and table index', condition, async () => { const result = await main({ __ow_logger: console, - __ow_path: '/https://adobe-my.sharepoint.com/personal/trieloff_adobe_com/_layouts/15/guestaccess.aspx', - share: 'Edz_l4D0BghJjLkIfyZCB7sBLaBhySyT5An7fPHVS6CFuA', - email: 'helix@adobe.com', - e: 'e5ziwf', + src: 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx?d=w6911fff4a52a4b3fb80560d8785adfa3&csf=1&web=1&e=fkkA2a', + sheet: 'tables', + table: 1, AZURE_WORD2MD_CLIENT_ID: process.env.AZURE_WORD2MD_CLIENT_ID, AZURE_HELIX_USER: process.env.AZURE_HELIX_USER, AZURE_HELIX_PASSWORD: process.env.AZURE_HELIX_PASSWORD, }); assert.equal(result.statusCode, 200); - assert.equal(result.body.length, 20); + assert.deepEqual(result.body, [{ X: 111, Y: 222, Z: 333 }, { X: 444, Y: 555, Z: 666 }]); }).timeout(15000); - condit('Retrieves Excel Spreadsheet via drive uri', condit.hasenv('AZURE_WORD2MD_CLIENT_ID', 'AZURE_HELIX_USER', 'AZURE_HELIX_PASSWORD'), async () => { + condit('Retrieves Excel Spreadsheet via drive uri', condition, async () => { const result = await main({ __ow_logger: console, - src: 'onedrive:/drives/b!2nFK7YhvL0yLgH3l_6DPvdBErfBlFFRPphB3wsFazXGX6gDR8muPTo89wY6LZLgv/items/01YELWHJW476LYB5AGBBEYZOIIP4TEEB53', - share: 'Edz_l4D0BghJjLkIfyZCB7sBLaBhySyT5An7fPHVS6CFuA', - email: 'helix@adobe.com', - e: 'e5ziwf', + src: 'onedrive:/drives/b!DyVXacYnlkm_17hZL307Me9vzRzaKwZCpVMBYbPOKaVT_gD5WmlHRbC-PCpiwGPx/items/012VWERI7U74IWSKVFH5F3QBLA3B4FVX5D', AZURE_WORD2MD_CLIENT_ID: process.env.AZURE_WORD2MD_CLIENT_ID, AZURE_HELIX_USER: process.env.AZURE_HELIX_USER, AZURE_HELIX_PASSWORD: process.env.AZURE_HELIX_PASSWORD, }); assert.equal(result.statusCode, 200); - assert.equal(result.body.length, 20); + assert.deepEqual(result.body, DATA_COUNTRIES); }).timeout(15000); }); diff --git a/test/excel.test.js b/test/excel.test.js index e1f9b9e2..3c838e2b 100644 --- a/test/excel.test.js +++ b/test/excel.test.js @@ -18,17 +18,25 @@ const path = require('path'); const fs = require('fs-extra'); const exampleBook = require('./fixtures/book-with-tables.js'); const exampleBook2 = require('./fixtures/book-without-tables.js'); +const exampleBook3 = require('./fixtures/book-without-default.js'); +const exampleBook4 = require('./fixtures/book-without-helix.js'); const TEST_SHARE_LINK = 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx'; const TEST_SHARE_LINK_NO_TABLES = 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data-no-tables.xlsx'; +const TEST_SHARE_LINK_NO_DEFAULT = 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data-no-default.xlsx'; +const TEST_SHARE_LINK_NO_HELIX = 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data-no-helix.xlsx'; class DummyOneDrive extends OneDriveMock { constructor() { super(); this.registerWorkbook('my-drive', 'my-item', exampleBook); this.registerWorkbook('my-drive', 'my-item-no-tables', exampleBook2); + this.registerWorkbook('my-drive', 'my-item-no-default', exampleBook3); + this.registerWorkbook('my-drive', 'my-item-no-helix', exampleBook4); this.registerShareLink(TEST_SHARE_LINK, 'my-drive', 'my-item'); this.registerShareLink(TEST_SHARE_LINK_NO_TABLES, 'my-drive', 'my-item-no-tables'); + this.registerShareLink(TEST_SHARE_LINK_NO_DEFAULT, 'my-drive', 'my-item-no-default'); + this.registerShareLink(TEST_SHARE_LINK_NO_HELIX, 'my-drive', 'my-item-no-helix'); } } @@ -39,6 +47,46 @@ describe('Excel Tests', () => { }, }); + it('Returns 404 for Excel Workbooks with no default sheet', async () => { + const result = await extract( + new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data-no-default.xlsx'), + {}, + ); + assert.equal(result.statusCode, 404); + }); + + it('Returns 404 for Excel Workbooks with wrong sheet name', async () => { + const result = await extract( + new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data-no-default.xlsx'), + { + sheet: 'foo', + }, + ); + assert.equal(result.statusCode, 404); + }); + + it('Works for Excel Workbooks with no default but sheet name', async () => { + const expected = await fs.readJson(path.resolve(__dirname, 'fixtures', 'example-data-sheet1.json')); + const result = await extract( + new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data-no-default.xlsx'), + { + sheet: 'countries', + }, + ); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, expected); + }); + + it('Works for Excel Workbooks with no helix sheet', async () => { + const expected = await fs.readJson(path.resolve(__dirname, 'fixtures', 'example-data-sheet1.json')); + const result = await extract( + new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data-no-helix.xlsx'), + {}, + ); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, expected); + }); + it('Works for Excel Workbooks with no tables', async () => { const expected = await fs.readJson(path.resolve(__dirname, 'fixtures', 'example-data-sheet1.json')); const result = await extract( @@ -59,16 +107,50 @@ describe('Excel Tests', () => { assert.deepEqual(result.body, expected); }); - it('Works for Excel Workbooks with tables', async () => { + it('Works for Excel Workbooks with tables (index)', async () => { const expected = await fs.readJson(path.resolve(__dirname, 'fixtures', 'example-data-sheet1.json')); const result = await extract( new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx'), - {}, + { + table: '0', + }, + ); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, expected); + }); + + it('Works for Excel Workbooks with tables (name)', async () => { + const expected = await fs.readJson(path.resolve(__dirname, 'fixtures', 'example-data-sheet1.json')); + const result = await extract( + new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx'), + { + table: 'Table1', + }, ); assert.equal(result.statusCode, 200); assert.deepEqual(result.body, expected); }); + it('Returns 404 for wrong table name', async () => { + const result = await extract( + new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx'), + { + table: 'foo', + }, + ); + assert.equal(result.statusCode, 404); + }); + + it('Returns 404 for wrong table index', async () => { + const result = await extract( + new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx'), + { + table: '99', + }, + ); + assert.equal(result.statusCode, 404); + }); + it('Fails with 404 for non existing Excel workbook', async () => { const result = await extract( new URL('https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/not-exist.xlsx'), diff --git a/test/fixtures/book-with-tables.js b/test/fixtures/book-with-tables.js index 8a7f2b8f..89e8ba74 100644 --- a/test/fixtures/book-with-tables.js +++ b/test/fixtures/book-with-tables.js @@ -18,8 +18,13 @@ const data = [ ['France', 'FR', 99], ]; +const data2 = [ + ['Country', 'Code', 'Number'], + ['Japan', 'JP', 81], +]; + const tables = [{ - name: 'table', + name: 'Table1', headerNames: data[0], rows: data.slice(1), }]; @@ -31,6 +36,15 @@ module.exports = { tables, sheets: [{ name: 'Sheet1', + tables: [], + namedItems, + usedRange: { + address: 'Sheet1!A1:B4', + addressLocal: 'A1:B4', + values: data2, + }, + }, { + name: 'helix-default', tables, namedItems, usedRange: { diff --git a/test/fixtures/book-without-default.js b/test/fixtures/book-without-default.js new file mode 100644 index 00000000..84816f31 --- /dev/null +++ b/test/fixtures/book-without-default.js @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +const data = [ + ['Country', 'Code', 'Number'], + ['Japan', 'JP', 3], + ['Germany', 'DE', 5], + ['USA', 'US', 7], + ['Switzerland', 'CH', 27], + ['France', 'FR', 99], +]; + +const tables = []; + +const namedItems = []; + +module.exports = { + name: 'book-without-default', + tables, + sheets: [{ + name: 'helix-countries', + tables, + namedItems, + usedRange: { + address: 'Sheet1!A1:B4', + addressLocal: 'A1:B4', + values: data, + }, + }], + namedItems, +}; diff --git a/test/fixtures/book-without-helix.js b/test/fixtures/book-without-helix.js new file mode 100644 index 00000000..966f701a --- /dev/null +++ b/test/fixtures/book-without-helix.js @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +const data = [ + ['Country', 'Code', 'Number'], + ['Japan', 'JP', 3], + ['Germany', 'DE', 5], + ['USA', 'US', 7], + ['Switzerland', 'CH', 27], + ['France', 'FR', 99], +]; + +const tables = []; + +const namedItems = []; + +module.exports = { + name: 'book-without-helix', + tables, + sheets: [{ + name: 'Sheet1', + tables, + namedItems, + usedRange: { + address: 'Sheet1!A1:B4', + addressLocal: 'A1:B4', + values: data, + }, + }], + namedItems, +}; diff --git a/test/fixtures/book-without-tables.js b/test/fixtures/book-without-tables.js index 25b7ded6..99280667 100644 --- a/test/fixtures/book-without-tables.js +++ b/test/fixtures/book-without-tables.js @@ -23,10 +23,10 @@ const tables = []; const namedItems = []; module.exports = { - name: 'book-with-tables', + name: 'book-without-tables', tables, sheets: [{ - name: 'Sheet1', + name: 'helix-default', tables, namedItems, usedRange: { diff --git a/test/google.test.js b/test/google.test.js index 2e318ab5..2d343b65 100644 --- a/test/google.test.js +++ b/test/google.test.js @@ -31,6 +31,84 @@ class MockOAuth2 { } } +const TEST_SHEETS_1 = { + data: { + spreadsheetId: '1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k', + properties: { + title: 'Autos', + locale: 'en_US', + autoRecalc: 'ON_CHANGE', + timeZone: 'Europe/Paris', + defaultFormat: { + backgroundColor: { red: 1, green: 1, blue: 1 }, + padding: { right: 3, left: 3 }, + verticalAlignment: 'BOTTOM', + wrapStrategy: 'LEGACY_WRAP', + textFormat: { + foregroundColor: {}, fontFamily: 'arial,sans,sans-serif', fontSize: 10, bold: false, italic: false, strikethrough: false, underline: false, foregroundColorStyle: { rgbColor: {} }, + }, + backgroundColorStyle: { rgbColor: { red: 1, green: 1, blue: 1 } }, + }, + }, + sheets: [{ + properties: { + sheetId: 0, title: 'helix-default', index: 0, sheetType: 'GRID', gridProperties: { rowCount: 92, columnCount: 20, frozenRowCount: 1 }, + }, + }], + spreadsheetUrl: 'https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit', + }, +}; + +const TEST_SHEETS_2 = { + data: { + spreadsheetId: '2222222222222222222', + properties: { + title: 'Autos', + }, + sheets: [{ + properties: { + sheetId: 0, title: 'Sheet1', index: 0, sheetType: 'GRID', gridProperties: { rowCount: 92, columnCount: 20, frozenRowCount: 1 }, + }, + }], + }, +}; + +const TEST_SHEETS_3 = { + data: { + spreadsheetId: '3333333333333333333', + properties: { + title: 'Autos', + }, + sheets: [{ + properties: { + sheetId: 0, title: 'helix-countries', index: 0, sheetType: 'GRID', gridProperties: { rowCount: 92, columnCount: 20, frozenRowCount: 1 }, + }, + }], + }, +}; + +const TEST_VALUES_1 = { + data: { range: 'helix-default!A1:T92', majorDimension: 'ROWS', values: [['Hersteller', 'Modell', 'Preis', 'Verbrauch', 'Kofferraum', 'Preis2', 'Verbrauch pro Jahr', 'Gesamtkosten'], ['Hyundai', 'Trajet', 23000, 7.2, 304, 1.1, 5976, 28976, 'Klapptüren', 'Hässlich wie die Nacht'], ['Ford', 'Galaxy Ambiente', 24400, 10.1, 330, 1.3, 8383, 32783, '193 km/h'], ['Fiat', 'Ulysse', 25200, 9.2, 324, 1.3, 7635.999999999999, 32836, 'Schiebetüren'], ['Citroën', 'C8', 25400, 9.1, 324, 1.3, 7553, 32953, 'Schiebetüren'], ['Peugeot', '807 2.0', 25450, 9.1, 324, 1.3, 7553, 33003, 'Schiebetüren'], ['Renault', 'Espace 2.0 Advantage', 25500, 9.4, 291, 1.3, 7802, 33302, 'kleiner Kofferraum'], ['Chrysler', 'Voyager Family', 25900, 10.1, 660, 1.3, 8383, 34283, 'Gegenübersitzen'], ['VW', 'Multivan Startline', 29000, 8, 664, 1.3, 6640, 35640, 'Gegenübersitzen'], ['Lancia', 'Phedra', 30900, 6.9, 324, 1.1, 5727, 36627], ['VW', 'Sharan 2.0', 29300, 9.4, 1200, 1.3, 7802, 37102], ['Mercedes-Benz', 'Viano Lang', 33150, 8.9, 730, 1.1, 7387, 40537], [], ['Hyundai', 'H-1 Travel', 20400, 8.3, '', 1.1, 6889.000000000001, 27289], ['Ford', 'Transit Cityline', 19611, 7, '', 1.1, 5810, 25421], ['Opel', 'Vivaro', 19660, 7.7, '', 1.1, 6391, 26051], ['Renault', 'Trafic', '', '', '', 1.1, 0, 0], ['Nissan', 'Primastar', '', '', '', 1.1, 0, 0], ['Citroën', 'Jumper', '', '', '', 1.1, 0, 0], ['Peugeot', 'Boxer', 19490, 7, '', 1.1, 5810, 25300], ['Fiat', 'Ducato', 19390, 7.3, '', 1.1, 6059, 25449]] }, +}; +const TEST_VALUES_2 = { + data: { range: 'Sheet1!A1:B4', majorDimension: 'ROWS', values: [['Country', 'Code', 'Number'], ['Japan', 'JP', '81']] }, +}; + +const TEST_VALUES_3 = { + data: { range: 'helix-countries!A1:B4', majorDimension: 'ROWS', values: [['Country', 'Code', 'Number'], ['Switzerland', 'CH', '41']] }, +}; + +const TEST_SHEETS = { + '1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k': TEST_SHEETS_1, + '2222222222222222222': TEST_SHEETS_2, + '3333333333333333333': TEST_SHEETS_3, +}; +const TEST_VALUES = { + '1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k': TEST_VALUES_1, + '2222222222222222222': TEST_VALUES_2, + '3333333333333333333': TEST_VALUES_3, +}; + describe('Google Sheets Tests (mocked)', () => { const { extract } = proxyquire('../src/matchers/google.js', { googleapis: { @@ -40,37 +118,9 @@ describe('Google Sheets Tests (mocked)', () => { }, sheets: () => ({ spreadsheets: { - get: () => ({ - data: { - spreadsheetId: '1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k', - properties: { - title: 'Autos', - locale: 'en_US', - autoRecalc: 'ON_CHANGE', - timeZone: 'Europe/Paris', - defaultFormat: { - backgroundColor: { red: 1, green: 1, blue: 1 }, - padding: { right: 3, left: 3 }, - verticalAlignment: 'BOTTOM', - wrapStrategy: 'LEGACY_WRAP', - textFormat: { - foregroundColor: {}, fontFamily: 'arial,sans,sans-serif', fontSize: 10, bold: false, italic: false, strikethrough: false, underline: false, foregroundColorStyle: { rgbColor: {} }, - }, - backgroundColorStyle: { rgbColor: { red: 1, green: 1, blue: 1 } }, - }, - }, - sheets: [{ - properties: { - sheetId: 0, title: 'Sheet1', index: 0, sheetType: 'GRID', gridProperties: { rowCount: 92, columnCount: 20, frozenRowCount: 1 }, - }, - }], - spreadsheetUrl: 'https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit', - }, - }), + get: ({ spreadsheetId }) => (TEST_SHEETS[spreadsheetId]), values: { - get: () => ({ - data: { range: 'Sheet1!A1:T92', majorDimension: 'ROWS', values: [['Hersteller', 'Modell', 'Preis', 'Verbrauch', 'Kofferraum', 'Preis2', 'Verbrauch pro Jahr', 'Gesamtkosten'], ['Hyundai', 'Trajet', 23000, 7.2, 304, 1.1, 5976, 28976, 'Klapptüren', 'Hässlich wie die Nacht'], ['Ford', 'Galaxy Ambiente', 24400, 10.1, 330, 1.3, 8383, 32783, '193 km/h'], ['Fiat', 'Ulysse', 25200, 9.2, 324, 1.3, 7635.999999999999, 32836, 'Schiebetüren'], ['Citroën', 'C8', 25400, 9.1, 324, 1.3, 7553, 32953, 'Schiebetüren'], ['Peugeot', '807 2.0', 25450, 9.1, 324, 1.3, 7553, 33003, 'Schiebetüren'], ['Renault', 'Espace 2.0 Advantage', 25500, 9.4, 291, 1.3, 7802, 33302, 'kleiner Kofferraum'], ['Chrysler', 'Voyager Family', 25900, 10.1, 660, 1.3, 8383, 34283, 'Gegenübersitzen'], ['VW', 'Multivan Startline', 29000, 8, 664, 1.3, 6640, 35640, 'Gegenübersitzen'], ['Lancia', 'Phedra', 30900, 6.9, 324, 1.1, 5727, 36627], ['VW', 'Sharan 2.0', 29300, 9.4, 1200, 1.3, 7802, 37102], ['Mercedes-Benz', 'Viano Lang', 33150, 8.9, 730, 1.1, 7387, 40537], [], ['Hyundai', 'H-1 Travel', 20400, 8.3, '', 1.1, 6889.000000000001, 27289], ['Ford', 'Transit Cityline', 19611, 7, '', 1.1, 5810, 25421], ['Opel', 'Vivaro', 19660, 7.7, '', 1.1, 6391, 26051], ['Renault', 'Trafic', '', '', '', 1.1, 0, 0], ['Nissan', 'Primastar', '', '', '', 1.1, 0, 0], ['Citroën', 'Jumper', '', '', '', 1.1, 0, 0], ['Peugeot', 'Boxer', 19490, 7, '', 1.1, 5810, 25300], ['Fiat', 'Ducato', 19390, 7.3, '', 1.1, 6059, 25449]] }, - }), + get: ({ spreadsheetId }) => (TEST_VALUES[spreadsheetId]), }, }, }), @@ -93,6 +143,58 @@ describe('Google Sheets Tests (mocked)', () => { assert.ok(result.body[0].Preis); }).timeout(15000); + it('Returns 404 for unknown sheet', async () => { + const result = await extract( + new URL('https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit?ouid=107837958797411838063&usp=sheets_home&ths=true'), + { + GOOGLE_DOCS2MD_CLIENT_ID: 'fake', + GOOGLE_DOCS2MD_CLIENT_SECRET: 'fake', + GOOGLE_DOCS2MD_REFRESH_TOKEN: 'fake', + sheet: 'foo', + }, + ); + assert.equal(result.statusCode, 404); + }).timeout(15000); + + it('Returns 404 for sheets with no helix-default', async () => { + const result = await extract( + new URL('https://docs.google.com/spreadsheets/d/3333333333333333333/edit?ouid=107837958797411838063&usp=sheets_home&ths=true'), + { + GOOGLE_DOCS2MD_CLIENT_ID: 'fake', + GOOGLE_DOCS2MD_CLIENT_SECRET: 'fake', + GOOGLE_DOCS2MD_REFRESH_TOKEN: 'fake', + }, + ); + assert.equal(result.statusCode, 404); + }).timeout(15000); + + it('Returns first sheet if no helix-sheets', async () => { + const result = await extract( + new URL('https://docs.google.com/spreadsheets/d/2222222222222222222/edit?ouid=107837958797411838063&usp=sheets_home&ths=true'), + { + GOOGLE_DOCS2MD_CLIENT_ID: 'fake', + GOOGLE_DOCS2MD_CLIENT_SECRET: 'fake', + GOOGLE_DOCS2MD_REFRESH_TOKEN: 'fake', + }, + ); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, [{ Code: 'JP', Country: 'Japan', Number: '81' }]); + }).timeout(15000); + + it('Returns correct sheet', async () => { + const result = await extract( + new URL('https://docs.google.com/spreadsheets/d/3333333333333333333/edit?ouid=107837958797411838063&usp=sheets_home&ths=true'), + { + GOOGLE_DOCS2MD_CLIENT_ID: 'fake', + GOOGLE_DOCS2MD_CLIENT_SECRET: 'fake', + GOOGLE_DOCS2MD_REFRESH_TOKEN: 'fake', + sheet: 'countries', + }, + ); + assert.equal(result.statusCode, 200); + assert.deepEqual(result.body, [{ Code: 'CH', Country: 'Switzerland', Number: '41' }]); + }).timeout(15000); + it('Fails (in a good way) for mocked Google Sheets', async () => { const result = await extract( new URL('https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit?ouid=107837958797411838063&usp=sheets_home&ths=true'), diff --git a/test/post-deploy.test.js b/test/post-deploy.test.js index df14dd5d..0ab9f78b 100644 --- a/test/post-deploy.test.js +++ b/test/post-deploy.test.js @@ -46,18 +46,20 @@ describe('Post-Deploy Tests', () => { }).timeout(10000); it('Excel Embed (without tables)', async () => { - console.log('Trying', 'https://adobe-my.sharepoint.com/personal/trieloff_adobe_com/_layouts/15/guestaccess.aspx?share=Edoi88tLKLpDsKzSfL-pcJYB2lIo7UKooYWnjm3w2WRrsA&email=helix%40adobe.com&e=tD623x'); + const url = 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx?d=w6911fff4a52a4b3fb80560d8785adfa3&csf=1&web=1&e=fkkA2a'; + console.log('Trying', url); await chai .request('https://adobeioruntime.net/') - .get(`${getbaseurl()}/https://adobe-my.sharepoint.com/personal/trieloff_adobe_com/_layouts/15/guestaccess.aspx?share=Edoi88tLKLpDsKzSfL-pcJYB2lIo7UKooYWnjm3w2WRrsA&email=helix%40adobe.com&e=tD623x`) + .get(`${getbaseurl()}/${url}`) .then((response) => { - // console.log(response.body); + // console.log(response); expect(response).to.have.status(200); expect(response).to.be.json; expect(response.body).to.be.an('array').that.deep.includes({ - project: 'Helix', - created: 2018, + Country: 'Japan', + Code: 'JP', + Number: 3, }); }).catch((e) => { throw e; @@ -65,24 +67,20 @@ describe('Post-Deploy Tests', () => { }).timeout(10000); it('Excel Embed (with tables)', async () => { - console.log('Trying', 'https://adobe-my.sharepoint.com/personal/trieloff_adobe_com/_layouts/15/guestaccess.aspx?share=Edz_l4D0BghJjLkIfyZCB7sBLaBhySyT5An7fPHVS6CFuA&email=helix%40adobe.com&e=e5ziwf'); + const url = 'https://adobe.sharepoint.com/:x:/r/sites/cg-helix/Shared%20Documents/data-embed-unit-tests/example-data.xlsx?d=w6911fff4a52a4b3fb80560d8785adfa3&csf=1&web=1&e=fkkA2a'; + console.log('Trying', url); await chai .request('https://adobeioruntime.net/') - .get(`${getbaseurl()}/https://adobe-my.sharepoint.com/personal/trieloff_adobe_com/_layouts/15/guestaccess.aspx?share=Edz_l4D0BghJjLkIfyZCB7sBLaBhySyT5An7fPHVS6CFuA&email=helix%40adobe.com&e=e5ziwf`) + .get(`${getbaseurl()}?src=${encodeURIComponent(url)}&sheet=tables&table=0`) .then((response) => { + console.log(response); expect(response).to.have.status(200); expect(response).to.be.json; expect(response.body).to.be.an('array').that.deep.includes({ - Column1: 'Klapptüren', - Gesamtkosten: 28976, - Hersteller: 'Hyundai', - Kofferraum: 304, - Modell: 'Trajet', - Preis: 23000, - Preis2: 1.1, - Verbrauch: 7.2, - 'Verbrauch pro Jahr': 5976, + A: 112, + B: 224, + C: 135, }); }).catch((e) => { throw e; @@ -90,15 +88,17 @@ describe('Post-Deploy Tests', () => { }).timeout(10000); it('Google Sheets Embed', async () => { - console.log('Trying', `https://adobeioruntime.net/${getbaseurl()}/https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit?ouid=107837958797411838063&usp=sheets_home&ths=true`); - + const url = 'https://docs.google.com/spreadsheets/d/1KP2-ty18PLmHMduBX-ZOlHUpNCk6uB1Q1i__l3scoTM/view'; + console.log('Trying', url); await chai .request('https://adobeioruntime.net/') - .get(`${getbaseurl()}/https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit?ouid=107837958797411838063&usp=sheets_home&ths=true`) + .get(`${getbaseurl()}/${url}`) .then((response) => { expect(response).to.be.json; expect(response.body).to.be.an('array').that.deep.includes({ - Hersteller: 'Hyundai', Modell: 'Trajet', Preis: 23000, Verbrauch: 7.2, Kofferraum: 304, Preis2: 1.1, 'Verbrauch pro Jahr': 5976, Gesamtkosten: 28976, + Country: 'Japan', + Code: 'JP', + Number: 3, }); expect(response).to.have.status(200); }).catch((e) => { @@ -107,24 +107,16 @@ describe('Post-Deploy Tests', () => { }).timeout(10000); it('Google Sheets Embed with Query Builder', async () => { - console.log('Trying', `https://adobeioruntime.net/${getbaseurl()}/https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit?ouid=107837958797411838063&usp=sheets_home&ths=true`); + const url = 'https://docs.google.com/spreadsheets/d/1KP2-ty18PLmHMduBX-ZOlHUpNCk6uB1Q1i__l3scoTM/edit?hlx_property=Code&hlx_property.value=DE'; + console.log('Trying', url); await chai .request('https://adobeioruntime.net/') - .get(`${getbaseurl()}/https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit?ouid=107837958797411838063&usp=sheets_home&ths=true&hlx_property=Hersteller&hlx_property.value=Ford`) + .get(`${getbaseurl()}/${url}`) .then((response) => { + console.log(response.body); expect(response).to.be.json; - expect(response.body).to.be.an('array').that.deep.includes({ - Gesamtkosten: 32783, - Hersteller: 'Ford', - Kofferraum: 330, - Modell: 'Galaxy Ambiente', - Preis: 24400, - Preis2: 1.3, - Verbrauch: 10.1, - 'Verbrauch pro Jahr': 8383, - }); - expect(response.body).to.have.lengthOf(2); + expect(response.body).to.be.an('array').that.eql([{ Code: 'DE', Country: 'Germany', Number: 5 }]); expect(response).to.have.status(200); }).catch((e) => { throw e; @@ -132,26 +124,17 @@ describe('Post-Deploy Tests', () => { }).timeout(10000); it('Google Sheets Embed with Query Builder (alternative syntax)', async () => { - const src = 'https://docs.google.com/spreadsheets/d/1IX0g5P74QnHPR3GW1AMCdTk_-m954A-FKZRT2uOZY7k/edit?ouid=107837958797411838063&usp=sheets_home&ths=true'; - const url = `${getbaseurl()}?src=${encodeURIComponent(src)}&hlx_property=Hersteller&hlx_property.value=Ford`; + const src = 'https://docs.google.com/spreadsheets/d/1KP2-ty18PLmHMduBX-ZOlHUpNCk6uB1Q1i__l3scoTM/edit'; + const url = `${getbaseurl()}?src=${encodeURIComponent(src)}&hlx_property=Code&hlx_property.value=DE`; console.log('Trying', url); await chai .request('https://adobeioruntime.net/') .get(url) .then((response) => { + console.log(response.body); expect(response).to.be.json; - expect(response.body).to.be.an('array').that.deep.includes({ - Gesamtkosten: 32783, - Hersteller: 'Ford', - Kofferraum: 330, - Modell: 'Galaxy Ambiente', - Preis: 24400, - Preis2: 1.3, - Verbrauch: 10.1, - 'Verbrauch pro Jahr': 8383, - }); - expect(response.body).to.have.lengthOf(2); + expect(response.body).to.be.an('array').that.eql([{ Code: 'DE', Country: 'Germany', Number: 5 }]); expect(response).to.have.status(200); }).catch((e) => { throw e;