mirror of
https://github.com/lowdefy/lowdefy.git
synced 2025-04-06 15:30:30 +08:00
feat: Init @lowdefy/connection-google-sheets package.
This commit is contained in:
parent
867a6db576
commit
4ffa94f8b7
24
.pnp.cjs
generated
24
.pnp.cjs
generated
@ -86,6 +86,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"name": "@lowdefy/connection-elasticsearch",
|
||||
"reference": "workspace:packages/connections/elasticsearch"
|
||||
},
|
||||
{
|
||||
"name": "@lowdefy/connection-google-sheets",
|
||||
"reference": "workspace:packages/connections/google-sheets"
|
||||
},
|
||||
{
|
||||
"name": "@lowdefy/connection-knex",
|
||||
"reference": "workspace:packages/connections/knex"
|
||||
@ -189,6 +193,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["@lowdefy/connection-aws", ["workspace:packages/connections/aws"]],
|
||||
["@lowdefy/connection-axios-http", ["virtual:c9d7c5a0f7602869dff02ed24b6a4fe62d4c9e4a4ede33ec34082ee9e4a5dd17f3e1bb396d56863e9bea8e8476d67351fe495fe7cebce9035a9e4de117e68169#workspace:packages/connections/axios-http", "workspace:packages/connections/axios-http"]],
|
||||
["@lowdefy/connection-elasticsearch", ["workspace:packages/connections/elasticsearch"]],
|
||||
["@lowdefy/connection-google-sheets", ["workspace:packages/connections/google-sheets"]],
|
||||
["@lowdefy/connection-knex", ["workspace:packages/connections/knex"]],
|
||||
["@lowdefy/connection-mongodb", ["workspace:packages/connections/mongodb"]],
|
||||
["@lowdefy/connection-sendgrid", ["workspace:packages/connections/sendgrid"]],
|
||||
@ -5239,6 +5244,25 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"linkType": "SOFT",
|
||||
}]
|
||||
]],
|
||||
["@lowdefy/connection-google-sheets", [
|
||||
["workspace:packages/connections/google-sheets", {
|
||||
"packageLocation": "./packages/connections/google-sheets/",
|
||||
"packageDependencies": [
|
||||
["@lowdefy/connection-google-sheets", "workspace:packages/connections/google-sheets"],
|
||||
["@babel/cli", "virtual:4a7337632ff6e9ee5a1c45a62a9ff4cc325a9367b21424babda93e269fe01b671e885bc41bdeebafb83c81f2a8eebbf0102043354a4e58905f61c8c3387cda1e#npm:7.15.7"],
|
||||
["@babel/core", "npm:7.15.8"],
|
||||
["@babel/preset-env", "virtual:4a7337632ff6e9ee5a1c45a62a9ff4cc325a9367b21424babda93e269fe01b671e885bc41bdeebafb83c81f2a8eebbf0102043354a4e58905f61c8c3387cda1e#npm:7.15.8"],
|
||||
["@lowdefy/ajv", "workspace:packages/ajv"],
|
||||
["@lowdefy/helpers", "workspace:packages/helpers"],
|
||||
["babel-jest", "virtual:4a7337632ff6e9ee5a1c45a62a9ff4cc325a9367b21424babda93e269fe01b671e885bc41bdeebafb83c81f2a8eebbf0102043354a4e58905f61c8c3387cda1e#npm:27.3.1"],
|
||||
["google-spreadsheet", "npm:3.1.15"],
|
||||
["jest", "npm:26.6.3"],
|
||||
["mingo", "npm:4.2.0"],
|
||||
["moment", "npm:2.29.1"]
|
||||
],
|
||||
"linkType": "SOFT",
|
||||
}]
|
||||
]],
|
||||
["@lowdefy/connection-knex", [
|
||||
["workspace:packages/connections/knex", {
|
||||
"packageLocation": "./packages/connections/knex/",
|
||||
|
12
packages/connections/google-sheets/.babelrc
Normal file
12
packages/connections/google-sheets/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "14"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
11
packages/connections/google-sheets/jest.config.js
Normal file
11
packages/connections/google-sheets/jest.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ['src/**/*.js'],
|
||||
coverageDirectory: 'coverage',
|
||||
coveragePathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/test/', '<rootDir>/src/index.js'],
|
||||
coverageReporters: [['lcov', { projectRoot: '../../..' }], 'text', 'clover'],
|
||||
errorOnDeprecated: true,
|
||||
testEnvironment: 'node',
|
||||
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/test/'],
|
||||
};
|
59
packages/connections/google-sheets/package.json
Normal file
59
packages/connections/google-sheets/package.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@lowdefy/connection-google-sheets",
|
||||
"version": "3.22.0",
|
||||
"licence": "Apache-2.0",
|
||||
"description": "",
|
||||
"homepage": "https://lowdefy.com",
|
||||
"keywords": [
|
||||
"lowdefy",
|
||||
"lowdefy connection"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://github.com/lowdefy/lowdefy/issues"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Sam Tolmay",
|
||||
"url": "https://github.com/SamTolmay"
|
||||
},
|
||||
{
|
||||
"name": "Gerrie van Wyk",
|
||||
"url": "https://github.com/Gervwyk"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lowdefy/lowdefy.git"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"scripts": {
|
||||
"babel": "babel src --out-dir dist",
|
||||
"build": "rm -rf dist && yarn babel",
|
||||
"clean": "rm -rf dist",
|
||||
"prepare": "yarn build",
|
||||
"test": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lowdefy/helpers": "3.22.0",
|
||||
"google-spreadsheet": "3.1.15",
|
||||
"mingo": "4.2.0",
|
||||
"moment": "2.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.15.7",
|
||||
"@babel/core": "7.15.8",
|
||||
"@babel/preset-env": "7.15.8",
|
||||
"@lowdefy/ajv": "3.22.0",
|
||||
"babel-jest": "27.3.1",
|
||||
"jest": "26.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@lowdefy/api": "3.22.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import schema from './GoogleSheetSchema.json';
|
||||
import GoogleSheetAppendMany from './GoogleSheetAppendMany/GoogleSheetAppendMany';
|
||||
import GoogleSheetAppendOne from './GoogleSheetAppendOne/GoogleSheetAppendOne';
|
||||
import GoogleSheetDeleteOne from './GoogleSheetDeleteOne/GoogleSheetDeleteOne';
|
||||
import GoogleSheetGetMany from './GoogleSheetGetMany/GoogleSheetGetMany';
|
||||
import GoogleSheetGetOne from './GoogleSheetGetOne/GoogleSheetGetOne';
|
||||
import GoogleSheetUpdateOne from './GoogleSheetUpdateOne/GoogleSheetUpdateOne';
|
||||
import GoogleSheetUpdateMany from './GoogleSheetUpdateMany/GoogleSheetUpdateMany';
|
||||
|
||||
export default {
|
||||
schema,
|
||||
requests: {
|
||||
GoogleSheetAppendMany,
|
||||
GoogleSheetAppendOne,
|
||||
GoogleSheetDeleteOne,
|
||||
GoogleSheetGetMany,
|
||||
GoogleSheetGetOne,
|
||||
GoogleSheetUpdateOne,
|
||||
GoogleSheetUpdateMany,
|
||||
},
|
||||
};
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { validate } from '@lowdefy/ajv';
|
||||
import GoogleSheet from './GoogleSheet';
|
||||
|
||||
const { schema } = GoogleSheet;
|
||||
|
||||
test('All requests are present', () => {
|
||||
expect(GoogleSheet.requests.GoogleSheetAppendOne).toBeDefined();
|
||||
expect(GoogleSheet.requests.GoogleSheetAppendMany).toBeDefined();
|
||||
expect(GoogleSheet.requests.GoogleSheetDeleteOne).toBeDefined();
|
||||
expect(GoogleSheet.requests.GoogleSheetGetMany).toBeDefined();
|
||||
expect(GoogleSheet.requests.GoogleSheetGetOne).toBeDefined();
|
||||
expect(GoogleSheet.requests.GoogleSheetUpdateOne).toBeDefined();
|
||||
expect(GoogleSheet.requests.GoogleSheetUpdateMany).toBeDefined();
|
||||
});
|
||||
|
||||
test('valid connection schema', () => {
|
||||
const connection = {
|
||||
apiKey: 'apiKey',
|
||||
sheetIndex: 0,
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
};
|
||||
expect(validate({ schema, data: connection })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('valid connection schema, all properties', () => {
|
||||
const connection = {
|
||||
apiKey: 'apiKey',
|
||||
client_email: 'client_email',
|
||||
private_key: 'private_key',
|
||||
sheetId: 'sheetId',
|
||||
sheetIndex: 0,
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
};
|
||||
expect(validate({ schema, data: connection })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('connection properties is not an object', () => {
|
||||
const connection = 'connection';
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection properties should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('spreadsheetId missing', () => {
|
||||
const connection = {};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection should have required property "spreadsheetId".'
|
||||
);
|
||||
});
|
||||
|
||||
test('spreadsheetId is not a string', () => {
|
||||
const connection = {
|
||||
spreadsheetId: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "spreadsheetId" should be a string.'
|
||||
);
|
||||
});
|
||||
|
||||
test('apiKey is not a string', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
apiKey: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "apiKey" should be a string.'
|
||||
);
|
||||
});
|
||||
|
||||
test('client_email is not a string', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
client_email: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "client_email" should be a string.'
|
||||
);
|
||||
});
|
||||
|
||||
test('private_key is not a string', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
private_key: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "private_key" should be a string.'
|
||||
);
|
||||
});
|
||||
|
||||
test('sheetId is not a string', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetId: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "sheetId" should be a string.'
|
||||
);
|
||||
});
|
||||
|
||||
test('sheetIndex is not a number', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetIndex: '',
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "sheetIndex" should be a number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('columnTypes is not an object', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
columnTypes: '',
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "columnTypes" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('columnTypes type is invalid', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
columnTypes: {
|
||||
column: 'invalid',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "/columnTypes/column" should be one of "string", "number", "boolean", "date", or "json".'
|
||||
);
|
||||
});
|
||||
|
||||
test('read is not a boolean', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
read: 'read',
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "read" should be a boolean.'
|
||||
);
|
||||
});
|
||||
|
||||
test('write is not a boolean', () => {
|
||||
const connection = {
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
write: 'write',
|
||||
};
|
||||
expect(() => validate({ schema, data: connection })).toThrow(
|
||||
'GoogleSheet connection property "write" should be a boolean.'
|
||||
);
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import schema from './GoogleSheetAppendManySchema.json';
|
||||
import getSheet from '../getSheet';
|
||||
import { transformWrite } from '../transformTypes';
|
||||
|
||||
async function googleSheetAppendMany({ request, connection }) {
|
||||
const { rows, options = {} } = request;
|
||||
const { raw } = options;
|
||||
const sheet = await getSheet({ connection });
|
||||
await sheet.addRows(transformWrite({ input: rows, types: connection.columnTypes }), { raw });
|
||||
return {
|
||||
insertedCount: rows.length,
|
||||
};
|
||||
}
|
||||
|
||||
export default { resolver: googleSheetAppendMany, schema, checkRead: false, checkWrite: true };
|
@ -0,0 +1,273 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { validate } from '@lowdefy/ajv';
|
||||
import GoogleSheetAppendMany from './GoogleSheetAppendMany';
|
||||
|
||||
const mockAddRows = jest.fn();
|
||||
jest.mock('../getSheet', () => () => ({
|
||||
addRows: mockAddRows,
|
||||
}));
|
||||
|
||||
const { resolver, schema, checkRead, checkWrite } = GoogleSheetAppendMany;
|
||||
const mockAddRowsDefaultImp = (rows) => rows.map((row) => ({ ...row, _sheet: {} }));
|
||||
|
||||
test('googleSheetAppendMany, one row', async () => {
|
||||
mockAddRows.mockImplementation(mockAddRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
rows: [{ id: '1', name: 'John', age: '34', birth_date: '2020/04/26', married: 'TRUE' }],
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({ insertedCount: 1 });
|
||||
expect(mockAddRows.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"age": "34",
|
||||
"birth_date": "2020/04/26",
|
||||
"id": "1",
|
||||
"married": "TRUE",
|
||||
"name": "John",
|
||||
},
|
||||
],
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetAppendMany, two rows', async () => {
|
||||
mockAddRows.mockImplementation(mockAddRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
rows: [
|
||||
{ id: '1', name: 'John', age: '34', birth_date: '2020/04/26', married: 'TRUE' },
|
||||
{ id: '2', name: 'Peter', age: '34', birth_date: '2020/04/26', married: 'TRUE' },
|
||||
],
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({ insertedCount: 2 });
|
||||
expect(mockAddRows.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"age": "34",
|
||||
"birth_date": "2020/04/26",
|
||||
"id": "1",
|
||||
"married": "TRUE",
|
||||
"name": "John",
|
||||
},
|
||||
Object {
|
||||
"age": "34",
|
||||
"birth_date": "2020/04/26",
|
||||
"id": "2",
|
||||
"married": "TRUE",
|
||||
"name": "Peter",
|
||||
},
|
||||
],
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetAppendMany, rows empty array', async () => {
|
||||
mockAddRows.mockImplementation(mockAddRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
rows: [],
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({ insertedCount: 0 });
|
||||
expect(mockAddRows.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Array [],
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetAppendMany, transform types', async () => {
|
||||
mockAddRows.mockImplementation(mockAddRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
rows: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: 34,
|
||||
birth_date: new Date('2020-04-26T00:00:00.000Z'),
|
||||
married: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
connection: {
|
||||
columnTypes: {
|
||||
name: 'string',
|
||||
age: 'number',
|
||||
married: 'boolean',
|
||||
birth_date: 'date',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).toEqual({ insertedCount: 1 });
|
||||
expect(mockAddRows.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"age": "34",
|
||||
"birth_date": "2020-04-26T00:00:00.000Z",
|
||||
"id": "1",
|
||||
"married": "TRUE",
|
||||
"name": "John",
|
||||
},
|
||||
],
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetAppendMany, one row, raw true', async () => {
|
||||
mockAddRows.mockImplementation(mockAddRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
rows: [{ id: '1', name: 'John', age: '34', birth_date: '2020/04/26', married: 'TRUE' }],
|
||||
options: {
|
||||
raw: true,
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({ insertedCount: 1 });
|
||||
expect(mockAddRows.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"age": "34",
|
||||
"birth_date": "2020/04/26",
|
||||
"id": "1",
|
||||
"married": "TRUE",
|
||||
"name": "John",
|
||||
},
|
||||
],
|
||||
Object {
|
||||
"raw": true,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('valid request schema', () => {
|
||||
const request = {
|
||||
rows: [
|
||||
{
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('valid request schema, all options', () => {
|
||||
const request = {
|
||||
rows: [
|
||||
{
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
options: {
|
||||
raw: true,
|
||||
},
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('request properties is not an object', () => {
|
||||
const request = 'request';
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendMany request properties should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('rows is not an array', () => {
|
||||
const request = {
|
||||
rows: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendMany request property "rows" should be an array.'
|
||||
);
|
||||
});
|
||||
|
||||
test('rows is not an array of objects', () => {
|
||||
const request = {
|
||||
rows: [1, 2, 3],
|
||||
};
|
||||
// Gives an error message for each item in array
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendMany request property "rows" should be an array of objects.; GoogleSheetAppendMany request property "rows" should be an array of objects.'
|
||||
);
|
||||
});
|
||||
|
||||
test('rows is missing', () => {
|
||||
const request = {};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendMany request should have required property "rows".'
|
||||
);
|
||||
});
|
||||
|
||||
test('raw is not a boolean', () => {
|
||||
const request = {
|
||||
rows: [
|
||||
{
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
options: {
|
||||
raw: 'raw',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendMany request property "options.raw" should be a boolean.'
|
||||
);
|
||||
});
|
||||
|
||||
test('checkRead should be false', async () => {
|
||||
expect(checkRead).toBe(false);
|
||||
});
|
||||
|
||||
test('checkWrite should be true', async () => {
|
||||
expect(checkWrite).toBe(true);
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Lowdefy Request Schema - GoogleSheetAppendMany",
|
||||
"type": "object",
|
||||
"required": ["rows"],
|
||||
"properties": {
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"description": "The rows to insert into the sheet. An an array of objects where keys are the column names and values are the values to insert.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetAppendMany request property \"rows\" should be an array."
|
||||
},
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "The row to insert into the sheet. An object where keys are the column names and values are the values to insert.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetAppendMany request property \"rows\" should be an array of objects."
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"raw": {
|
||||
"type": "boolean",
|
||||
"description": "Store raw values instead of converting as if typed into the sheets UI.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetAppendMany request property \"options.raw\" should be a boolean."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetAppendMany request properties should be an object.",
|
||||
"required": {
|
||||
"rows": "GoogleSheetAppendMany request should have required property \"rows\"."
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import schema from './GoogleSheetAppendOneSchema.json';
|
||||
import getSheet from '../getSheet';
|
||||
import cleanRows from '../cleanRows';
|
||||
import { transformWrite } from '../transformTypes';
|
||||
|
||||
async function googleSheetAppendOne({ request, connection }) {
|
||||
const { row, options = {} } = request;
|
||||
const { raw } = options;
|
||||
const sheet = await getSheet({ connection });
|
||||
const insertedRow = await sheet.addRow(
|
||||
transformWrite({ input: row, types: connection.columnTypes }),
|
||||
{ raw }
|
||||
);
|
||||
return {
|
||||
insertedCount: 1,
|
||||
row: cleanRows(insertedRow),
|
||||
};
|
||||
}
|
||||
|
||||
export default { resolver: googleSheetAppendOne, schema, checkRead: false, checkWrite: true };
|
@ -0,0 +1,216 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { validate } from '@lowdefy/ajv';
|
||||
import GoogleSheetAppendOne from './GoogleSheetAppendOne';
|
||||
|
||||
const mockAddRow = jest.fn();
|
||||
jest.mock('../getSheet', () => () => ({
|
||||
addRow: mockAddRow,
|
||||
}));
|
||||
|
||||
const { resolver, schema, checkRead, checkWrite } = GoogleSheetAppendOne;
|
||||
const mockAddRowDefaultImp = (row) => ({ ...row, _sheet: {} });
|
||||
|
||||
test('googleSheetAppendOne', async () => {
|
||||
mockAddRow.mockImplementation(mockAddRowDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
row: { id: '1', name: 'John', age: '34', birth_date: '2020/04/26', married: 'TRUE' },
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
insertedCount: 1,
|
||||
row: {
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
},
|
||||
});
|
||||
expect(mockAddRow.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"age": "34",
|
||||
"birth_date": "2020/04/26",
|
||||
"id": "1",
|
||||
"married": "TRUE",
|
||||
"name": "John",
|
||||
},
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetAppendOne, transform types', async () => {
|
||||
mockAddRow.mockImplementation(mockAddRowDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
row: {
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: 34,
|
||||
birth_date: new Date('2020-04-26T00:00:00.000Z'),
|
||||
married: true,
|
||||
},
|
||||
},
|
||||
connection: {
|
||||
columnTypes: {
|
||||
name: 'string',
|
||||
age: 'number',
|
||||
married: 'boolean',
|
||||
birth_date: 'date',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
insertedCount: 1,
|
||||
row: {
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020-04-26T00:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
},
|
||||
});
|
||||
expect(mockAddRow.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"age": "34",
|
||||
"birth_date": "2020-04-26T00:00:00.000Z",
|
||||
"id": "1",
|
||||
"married": "TRUE",
|
||||
"name": "John",
|
||||
},
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetAppendOne, raw true', async () => {
|
||||
mockAddRow.mockImplementation(mockAddRowDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
row: { id: '1', name: 'John', age: '34', birth_date: '2020/04/26', married: 'TRUE' },
|
||||
options: {
|
||||
raw: true,
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
insertedCount: 1,
|
||||
row: {
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
},
|
||||
});
|
||||
expect(mockAddRow.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"age": "34",
|
||||
"birth_date": "2020/04/26",
|
||||
"id": "1",
|
||||
"married": "TRUE",
|
||||
"name": "John",
|
||||
},
|
||||
Object {
|
||||
"raw": true,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('valid request schema', () => {
|
||||
const request = {
|
||||
row: {
|
||||
name: 'name',
|
||||
},
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('valid request schema, all options', () => {
|
||||
const request = {
|
||||
row: {
|
||||
name: 'name',
|
||||
},
|
||||
options: {
|
||||
raw: true,
|
||||
},
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('request properties is not an object', () => {
|
||||
const request = 'request';
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendOne request properties should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('row is not an object', () => {
|
||||
const request = {
|
||||
row: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendOne request property "row" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('row is missing', () => {
|
||||
const request = {};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendOne request should have required property "row".'
|
||||
);
|
||||
});
|
||||
|
||||
test('raw is not a boolean', () => {
|
||||
const request = {
|
||||
row: {
|
||||
name: 'name',
|
||||
},
|
||||
options: {
|
||||
raw: 'raw',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetAppendOne request property "options.raw" should be a boolean.'
|
||||
);
|
||||
});
|
||||
|
||||
test('checkRead should be false', async () => {
|
||||
expect(checkRead).toBe(false);
|
||||
});
|
||||
|
||||
test('checkWrite should be true', async () => {
|
||||
expect(checkWrite).toBe(true);
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Lowdefy Request Schema - GoogleSheetAppendOne",
|
||||
"type": "object",
|
||||
"required": ["row"],
|
||||
"properties": {
|
||||
"row": {
|
||||
"type": "object",
|
||||
"description": "The row to insert into the sheet. An object where keys are the column names and values are the values to insert.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetAppendOne request property \"row\" should be an object."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"raw": {
|
||||
"type": "boolean",
|
||||
"description": "Store raw values instead of converting as if typed into the sheets UI.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetAppendOne request property \"options.raw\" should be a boolean."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetAppendOne request properties should be an object.",
|
||||
"required": {
|
||||
"row": "GoogleSheetAppendOne request should have required property \"row\"."
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import schema from './GoogleSheetDeleteOneSchema.json';
|
||||
import cleanRows from '../cleanRows';
|
||||
import getSheet from '../getSheet';
|
||||
import { transformRead } from '../transformTypes';
|
||||
import mingoFilter from '../mingoFilter';
|
||||
|
||||
async function googleSheetDeleteOne({ request, connection }) {
|
||||
const { filter, options = {} } = request;
|
||||
const { limit, skip } = options;
|
||||
const sheet = await getSheet({ connection });
|
||||
let rows = await sheet.getRows({ limit, offset: skip });
|
||||
rows = transformRead({ input: rows, types: connection.columnTypes });
|
||||
rows = mingoFilter({ input: rows, filter });
|
||||
if (rows.length === 0) {
|
||||
return {
|
||||
deletedCount: 0,
|
||||
};
|
||||
}
|
||||
const row = rows[0];
|
||||
await row.delete();
|
||||
return {
|
||||
deletedCount: 1,
|
||||
row: cleanRows(row),
|
||||
};
|
||||
}
|
||||
|
||||
export default { resolver: googleSheetDeleteOne, schema, checkRead: false, checkWrite: true };
|
@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { validate } from '@lowdefy/ajv';
|
||||
import GoogleSheetDeleteOne from './GoogleSheetDeleteOne';
|
||||
|
||||
const mockGetRows = jest.fn();
|
||||
const mockDelete = jest.fn();
|
||||
jest.mock('../getSheet', () => () => ({
|
||||
getRows: mockGetRows,
|
||||
}));
|
||||
|
||||
const mockGetRowsDefaultImp = ({ limit, offset }) => {
|
||||
const rows = [
|
||||
{
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
delete: mockDelete,
|
||||
},
|
||||
{
|
||||
_rowNumber: 3,
|
||||
_rawData: ['2', 'Steven', '43', '2020/04/27', 'FALSE'],
|
||||
id: '2',
|
||||
name: 'Steve',
|
||||
age: '43',
|
||||
birth_date: '2020/04/27',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
delete: mockDelete,
|
||||
},
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
delete: mockDelete,
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-25T22:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-25T22:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
delete: mockDelete,
|
||||
},
|
||||
];
|
||||
return Promise.resolve(rows.slice(offset).slice(undefined, limit));
|
||||
};
|
||||
|
||||
const { resolver, schema, checkRead, checkWrite } = GoogleSheetDeleteOne;
|
||||
|
||||
test('googleSheetDeleteMany, match one', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: '1' },
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
deletedCount: 1,
|
||||
row: {
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
delete: mockDelete,
|
||||
},
|
||||
});
|
||||
expect(mockDelete).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('googleSheetDeleteMany, match nothing', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: 'does_not_exist' },
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
deletedCount: 0,
|
||||
});
|
||||
expect(mockDelete).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('googleSheetDeleteMany, match more than one', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { _rowNumber: { $gt: 3 } },
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
deletedCount: 1,
|
||||
row: {
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
_rowNumber: 4,
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
delete: mockDelete,
|
||||
id: '3',
|
||||
married: 'FALSE',
|
||||
name: 'Tim',
|
||||
},
|
||||
});
|
||||
expect(mockDelete).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('valid request schema', () => {
|
||||
const request = {
|
||||
filter: { id: '1' },
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('request properties is not an object', () => {
|
||||
const request = 'request';
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetDeleteOne request properties should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('filter is not an object', () => {
|
||||
const request = {
|
||||
filter: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetDeleteOne request property "filter" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('filter is missing', () => {
|
||||
const request = {};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetDeleteOne request should have required property "filter".'
|
||||
);
|
||||
});
|
||||
|
||||
test('limit is not a number', () => {
|
||||
const request = {
|
||||
options: {
|
||||
limit: true,
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetDeleteOne request property "options.limit" should be a number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('skip is not a number', () => {
|
||||
const request = {
|
||||
options: {
|
||||
skip: true,
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetDeleteOne request property "options.skip" should be a number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('checkRead should be false', async () => {
|
||||
expect(checkRead).toBe(false);
|
||||
});
|
||||
|
||||
test('checkWrite should be true', async () => {
|
||||
expect(checkWrite).toBe(true);
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Lowdefy Request Schema - GoogleSheetDeleteOne",
|
||||
"type": "object",
|
||||
"required": ["filter"],
|
||||
"properties": {
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"description": "A MongoDB query expression to filter the data. The first row matched by the filter will be deleted.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetDeleteOne request property \"filter\" should be an object."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"description": "The maximum number of rows to fetch.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetDeleteOne request property \"options.limit\" should be a number."
|
||||
}
|
||||
},
|
||||
"skip": {
|
||||
"type": "number",
|
||||
"description": "The number of rows to skip from the top of the sheet.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetDeleteOne request property \"options.skip\" should be a number."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetDeleteOne request properties should be an object.",
|
||||
"required": {
|
||||
"filter": "GoogleSheetDeleteOne request should have required property \"filter\"."
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import schema from './GoogleSheetGetManySchema.json';
|
||||
import cleanRows from '../cleanRows';
|
||||
import getSheet from '../getSheet';
|
||||
import { transformRead } from '../transformTypes';
|
||||
import mingoAggregation from '../mingoAggregation';
|
||||
import mingoFilter from '../mingoFilter';
|
||||
|
||||
async function googleSheetGetMany({ request, connection }) {
|
||||
const { filter, pipeline, options = {} } = request;
|
||||
const { limit, skip } = options;
|
||||
const sheet = await getSheet({ connection });
|
||||
let rows = await sheet.getRows({ limit, offset: skip });
|
||||
rows = cleanRows(rows);
|
||||
rows = transformRead({ input: rows, types: connection.columnTypes });
|
||||
if (filter) {
|
||||
rows = mingoFilter({ input: rows, filter });
|
||||
}
|
||||
if (pipeline) {
|
||||
rows = mingoAggregation({ input: rows, pipeline });
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
export default { resolver: googleSheetGetMany, schema, checkRead: true, checkWrite: false };
|
@ -0,0 +1,371 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { validate } from '@lowdefy/ajv';
|
||||
import GoogleSheetGetMany from './GoogleSheetGetMany';
|
||||
|
||||
const mockGetRows = jest.fn();
|
||||
jest.mock('../getSheet', () => () => ({
|
||||
getRows: mockGetRows,
|
||||
}));
|
||||
|
||||
const { resolver, schema, checkRead, checkWrite } = GoogleSheetGetMany;
|
||||
const mockGetRowsDefaultImp = ({ limit, offset }) => {
|
||||
const rows = [
|
||||
{
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
},
|
||||
{
|
||||
_rowNumber: 3,
|
||||
_rawData: ['2', 'Steven', '43', '2020/04/27', 'FALSE'],
|
||||
id: '2',
|
||||
name: 'Steve',
|
||||
age: '43',
|
||||
birth_date: '2020/04/27',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
},
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-26T00:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-26T00:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
},
|
||||
];
|
||||
return Promise.resolve(rows.slice(offset).slice(undefined, limit));
|
||||
};
|
||||
|
||||
test('googleSheetGetMany, all rows', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: {}, connection: {} });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
},
|
||||
{
|
||||
_rowNumber: 3,
|
||||
_rawData: ['2', 'Steven', '43', '2020/04/27', 'FALSE'],
|
||||
id: '2',
|
||||
name: 'Steve',
|
||||
age: '43',
|
||||
birth_date: '2020/04/27',
|
||||
married: 'FALSE',
|
||||
},
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-26T00:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-26T00:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, empty rows returned', async () => {
|
||||
mockGetRows.mockImplementation(() => []);
|
||||
const res = await resolver({ request: {}, connection: {} });
|
||||
expect(res).toEqual([]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, limit', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { options: { limit: 2 } }, connection: {} });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
},
|
||||
{
|
||||
_rowNumber: 3,
|
||||
_rawData: ['2', 'Steven', '43', '2020/04/27', 'FALSE'],
|
||||
id: '2',
|
||||
name: 'Steve',
|
||||
age: '43',
|
||||
birth_date: '2020/04/27',
|
||||
married: 'FALSE',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, skip', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { options: { skip: 2 } }, connection: {} });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-26T00:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-26T00:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, skip and limit', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { options: { skip: 2, limit: 1 } }, connection: {} });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, filter', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { filter: { name: 'Tim' } }, connection: {} });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, filter filters all', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { filter: { name: 'Nobody' } }, connection: {} });
|
||||
expect(res).toEqual([]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, filter _rowNumber', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { filter: { _rowNumber: { $gt: 3 } } }, connection: {} });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-26T00:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-26T00:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, pipeline count', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: { pipeline: [{ $group: { _id: 0, count: { $sum: 1 } } }] },
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_id: 0,
|
||||
count: 4,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('googleSheetGetMany, columnTypes', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {},
|
||||
connection: {
|
||||
columnTypes: {
|
||||
name: 'string',
|
||||
age: 'number',
|
||||
married: 'boolean',
|
||||
birth_date: 'date',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: 34,
|
||||
birth_date: new Date('2020-04-26T00:00:00.000Z'),
|
||||
married: true,
|
||||
},
|
||||
{
|
||||
_rowNumber: 3,
|
||||
_rawData: ['2', 'Steven', '43', '2020/04/27', 'FALSE'],
|
||||
id: '2',
|
||||
name: 'Steve',
|
||||
age: 43,
|
||||
birth_date: new Date('2020-04-27T00:00:00.000Z'),
|
||||
married: false,
|
||||
},
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: 34,
|
||||
birth_date: new Date('2020-04-28T00:00:00.000Z'),
|
||||
married: false,
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-26T00:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: 120,
|
||||
birth_date: new Date('2020-04-26T00:00:00.000Z'),
|
||||
married: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('valid request schema', () => {
|
||||
const request = {};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('valid request schema, all properties', () => {
|
||||
const request = {
|
||||
filter: { id: 1 },
|
||||
pipeline: [{ $addFields: { a: 1 } }],
|
||||
options: {
|
||||
limit: 100,
|
||||
skip: 300,
|
||||
},
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('request properties is not an object', () => {
|
||||
const request = 'request';
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetMany request properties should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('limit is not a number', () => {
|
||||
const request = {
|
||||
options: {
|
||||
limit: true,
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetMany request property "options.limit" should be a number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('skip is not a number', () => {
|
||||
const request = {
|
||||
options: {
|
||||
skip: true,
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetMany request property "options.skip" should be a number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('filter is not an object', () => {
|
||||
const request = {
|
||||
filter: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetMany request property "filter" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('pipeline is not an array', () => {
|
||||
const request = {
|
||||
pipeline: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetMany request property "pipeline" should be an array.'
|
||||
);
|
||||
});
|
||||
|
||||
test('checkRead should be true', async () => {
|
||||
expect(checkRead).toBe(true);
|
||||
});
|
||||
|
||||
test('checkWrite should be false', async () => {
|
||||
expect(checkWrite).toBe(false);
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Lowdefy Request Schema - GoogleSheetGetMany",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"description": "A MongoDB query expression to filter the data.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetMany request property \"filter\" should be an object."
|
||||
}
|
||||
},
|
||||
"pipeline": {
|
||||
"type": "array",
|
||||
"description": "A MongoDB aggregation pipeline to transform the data.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetMany request property \"pipeline\" should be an array."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"description": "The maximum number of rows to fetch.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetMany request property \"options.limit\" should be a number."
|
||||
}
|
||||
},
|
||||
"skip": {
|
||||
"type": "number",
|
||||
"description": "The number of rows to skip from the top of the sheet.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetMany request property \"options.skip\" should be a number."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetMany request properties should be an object."
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import schema from './GoogleSheetGetOneSchema.json';
|
||||
import cleanRows from '../cleanRows';
|
||||
import getSheet from '../getSheet';
|
||||
import { transformRead } from '../transformTypes';
|
||||
import mingoFilter from '../mingoFilter';
|
||||
|
||||
async function googleSheetGetOne({ request, connection }) {
|
||||
const { filter, options = {} } = request;
|
||||
const { limit, skip } = options;
|
||||
const sheet = await getSheet({ connection });
|
||||
let rows = await sheet.getRows({ limit, offset: skip });
|
||||
rows = cleanRows(rows);
|
||||
rows = transformRead({ input: rows, types: connection.columnTypes });
|
||||
if (filter) {
|
||||
rows = mingoFilter({ input: rows, filter });
|
||||
}
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export default { resolver: googleSheetGetOne, schema, checkRead: true, checkWrite: false };
|
@ -0,0 +1,277 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { validate } from '@lowdefy/ajv';
|
||||
import GoogleSheetGetOne from './GoogleSheetGetOne';
|
||||
|
||||
const mockGetRows = jest.fn();
|
||||
jest.mock('../getSheet', () => () => ({
|
||||
getRows: mockGetRows,
|
||||
}));
|
||||
|
||||
const { resolver, schema, checkRead, checkWrite } = GoogleSheetGetOne;
|
||||
const mockGetRowsDefaultImp = ({ limit, offset }) => {
|
||||
const rows = [
|
||||
{
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
},
|
||||
{
|
||||
_rowNumber: 3,
|
||||
_rawData: ['2', 'Steven', '43', '2020/04/27', 'FALSE'],
|
||||
id: '2',
|
||||
name: 'Steve',
|
||||
age: '43',
|
||||
birth_date: '2020/04/27',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
},
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-25T22:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-25T22:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
},
|
||||
];
|
||||
return Promise.resolve(rows.slice(offset).slice(undefined, limit));
|
||||
};
|
||||
|
||||
test('googleSheetGetOne, first row is returned', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: {}, connection: {} });
|
||||
expect(res).toEqual({
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
});
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, empty rows returned', async () => {
|
||||
mockGetRows.mockImplementation(() => []);
|
||||
const res = await resolver({ request: {}, connection: {} });
|
||||
expect(res).toEqual(null);
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, limit', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { options: { limit: 2 } }, connection: {} });
|
||||
expect(res).toEqual({
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
});
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, skip', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { options: { skip: 2 } }, connection: {} });
|
||||
expect(res).toEqual({
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
});
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, skip and limit', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { options: { skip: 2, limit: 1 } }, connection: {} });
|
||||
expect(res).toEqual({
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
});
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, filter', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { filter: { name: 'Tim' } }, connection: {} });
|
||||
expect(res).toEqual({
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
});
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, limit before filter', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: { filter: { name: 'Tim' }, options: { limit: 2 } },
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual(null);
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, skip before filter', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: { filter: { married: 'TRUE' }, options: { skip: 2 } },
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-25T22:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-25T22:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
});
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, filter filters all', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { filter: { name: 'Nobody' } }, connection: {} });
|
||||
expect(res).toEqual(null);
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, filter _rowNumber', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({ request: { filter: { _rowNumber: { $gt: 3 } } }, connection: {} });
|
||||
expect(res).toEqual({
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
});
|
||||
});
|
||||
|
||||
test('googleSheetGetOne, columnTypes', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {},
|
||||
connection: {
|
||||
columnTypes: {
|
||||
name: 'string',
|
||||
age: 'number',
|
||||
married: 'boolean',
|
||||
birth_date: 'date',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: 34,
|
||||
birth_date: new Date('2020-04-26T00:00:00.000Z'),
|
||||
married: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('valid request schema', () => {
|
||||
const request = {};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('valid request schema, all properties', () => {
|
||||
const request = {
|
||||
options: {
|
||||
limit: 100,
|
||||
skip: 300,
|
||||
},
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('request properties is not an object', () => {
|
||||
const request = 'request';
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetOne request properties should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('limit is not a number', () => {
|
||||
const request = {
|
||||
options: {
|
||||
limit: true,
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetOne request property "options.limit" should be a number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('skip is not a number', () => {
|
||||
const request = {
|
||||
options: {
|
||||
skip: true,
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetOne request property "options.skip" should be a number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('filter is not an object', () => {
|
||||
const request = {
|
||||
filter: true,
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetGetOne request property "filter" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('checkRead should be true', async () => {
|
||||
expect(checkRead).toBe(true);
|
||||
});
|
||||
|
||||
test('checkWrite should be false', async () => {
|
||||
expect(checkWrite).toBe(false);
|
||||
});
|
@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Lowdefy Request Schema - GoogleSheetGetOne",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"description": "A MongoDB query expression to filter the data.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetOne request property \"filter\" should be an object."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"description": "The maximum number of rows to fetch.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetOne request property \"options.limit\" should be a number."
|
||||
}
|
||||
},
|
||||
"skip": {
|
||||
"type": "number",
|
||||
"description": "The number of rows to skip from the top of the sheet.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetOne request property \"options.skip\" should be a number."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetGetOne request properties should be an object."
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Lowdefy Connection Schema - GoogleSheet",
|
||||
"type": "object",
|
||||
"required": ["spreadsheetId"],
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"type": "string",
|
||||
"description": "API key for your google project.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"apiKey\" should be a string."
|
||||
}
|
||||
},
|
||||
"client_email": {
|
||||
"type": "string",
|
||||
"description": "The email of your service account.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"client_email\" should be a string."
|
||||
}
|
||||
},
|
||||
"private_key": {
|
||||
"type": "string",
|
||||
"description": "The private key for your service account.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"private_key\" should be a string."
|
||||
}
|
||||
},
|
||||
"sheetId": {
|
||||
"type": "string",
|
||||
"description": "The ID of the worksheet. Can be found in the URL as the \"gid\" parameter. One of \"sheetId\" or \"sheetIndex\" is required.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"sheetId\" should be a string."
|
||||
}
|
||||
},
|
||||
"sheetIndex": {
|
||||
"type": "number",
|
||||
"description": "The position of the worksheet as they appear in the Google sheets UI. Starts from 0. One of \"sheetId\" or \"sheetIndex\" is required.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"sheetIndex\" should be a number."
|
||||
}
|
||||
},
|
||||
"spreadsheetId": {
|
||||
"type": "string",
|
||||
"description": "document ID from the URL of the spreadsheet.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"spreadsheetId\" should be a string."
|
||||
}
|
||||
},
|
||||
"columnTypes": {
|
||||
"type": "object",
|
||||
"description": "Define types for columns in the spreadsheet.",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"type": "string",
|
||||
"enum": ["string", "number", "boolean", "date", "json"],
|
||||
"errorMessage": {
|
||||
"enum": "GoogleSheet connection property \"{{ dataPath }}\" should be one of \"string\", \"number\", \"boolean\", \"date\", or \"json\"."
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"columnTypes\" should be an object."
|
||||
}
|
||||
},
|
||||
"read": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Allow reads from the spreadsheet.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"read\" should be a boolean."
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Allow writes to the spreadsheet.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection property \"write\" should be a boolean."
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheet connection properties should be an object.",
|
||||
"required": {
|
||||
"spreadsheetId": "GoogleSheet connection should have required property \"spreadsheetId\"."
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import schema from './GoogleSheetUpdateManySchema.json';
|
||||
import getSheet from '../getSheet';
|
||||
import { transformRead, transformWrite } from '../transformTypes';
|
||||
import mingoFilter from '../mingoFilter';
|
||||
|
||||
async function googleSheetUpdateMany({ request, connection }) {
|
||||
const { filter, update, options = {} } = request;
|
||||
const { limit, skip, raw } = options;
|
||||
const sheet = await getSheet({ connection });
|
||||
let rows = await sheet.getRows({ limit, offset: skip });
|
||||
rows = transformRead({ input: rows, types: connection.columnTypes });
|
||||
rows = mingoFilter({ input: rows, filter });
|
||||
const transformedUpdate = transformWrite({ input: update, types: connection.columnTypes });
|
||||
if (rows.length === 0) {
|
||||
return {
|
||||
modifiedCount: 0,
|
||||
};
|
||||
}
|
||||
const promises = rows.map(async (row) => {
|
||||
Object.assign(row, transformedUpdate);
|
||||
await row.save({ raw });
|
||||
});
|
||||
await Promise.all(promises);
|
||||
return {
|
||||
modifiedCount: rows.length,
|
||||
};
|
||||
}
|
||||
|
||||
export default { resolver: googleSheetUpdateMany, schema, checkRead: false, checkWrite: true };
|
@ -0,0 +1,258 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { validate } from '@lowdefy/ajv';
|
||||
import GoogleSheetUpdateMany from './GoogleSheetUpdateMany';
|
||||
|
||||
const mockGetRows = jest.fn();
|
||||
const mockSave = jest.fn();
|
||||
jest.mock('../getSheet', () => () => ({
|
||||
getRows: mockGetRows,
|
||||
}));
|
||||
|
||||
const { resolver, schema, checkRead, checkWrite } = GoogleSheetUpdateMany;
|
||||
|
||||
const mockGetRowsDefaultImp = ({ limit, offset }) => {
|
||||
const rows = [
|
||||
{
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
save: mockSave,
|
||||
},
|
||||
{
|
||||
_rowNumber: 3,
|
||||
_rawData: ['2', 'Steven', '43', '2020/04/27', 'FALSE'],
|
||||
id: '2',
|
||||
name: 'Steve',
|
||||
age: '43',
|
||||
birth_date: '2020/04/27',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
save: mockSave,
|
||||
},
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
save: mockSave,
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-25T22:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-25T22:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
save: mockSave,
|
||||
},
|
||||
];
|
||||
return Promise.resolve(rows.slice(offset).slice(undefined, limit));
|
||||
};
|
||||
|
||||
test('googleSheetUpdateMany, match one', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: '1' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 1,
|
||||
});
|
||||
expect(mockSave.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetUpdateMany, match one, raw true', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: '1' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
options: {
|
||||
raw: true,
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 1,
|
||||
});
|
||||
expect(mockSave.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"raw": true,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetUpdateMany, match nothing', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: 'does_not_exist' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 0,
|
||||
});
|
||||
expect(mockSave.mock.calls).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
|
||||
test('googleSheetUpdateMany, match more than one', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { _rowNumber: { $gt: 3 } },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 2,
|
||||
});
|
||||
expect(mockSave.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('valid request schema', () => {
|
||||
const request = {
|
||||
filter: { id: '1' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('request properties is not an object', () => {
|
||||
const request = 'request';
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateMany request properties should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('filter is not an object', () => {
|
||||
const request = {
|
||||
filter: 'filter',
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateMany request property "filter" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('update is not an object', () => {
|
||||
const request = {
|
||||
filter: { id: '1' },
|
||||
update: 'update',
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateMany request property "update" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('filter is missing', () => {
|
||||
const request = {
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateMany request should have required property "filter".'
|
||||
);
|
||||
});
|
||||
|
||||
test('update is missing', () => {
|
||||
const request = {
|
||||
filter: { id: '1' },
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateMany request should have required property "update".'
|
||||
);
|
||||
});
|
||||
|
||||
test('options.raw is not a boolean', () => {
|
||||
const request = {
|
||||
filter: 'filter',
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
options: {
|
||||
raw: 'raw',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateMany request property "options.raw" should be a boolean.'
|
||||
);
|
||||
});
|
||||
|
||||
test('checkRead should be false', async () => {
|
||||
expect(checkRead).toBe(false);
|
||||
});
|
||||
|
||||
test('checkWrite should be true', async () => {
|
||||
expect(checkWrite).toBe(true);
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Lowdefy Request Schema - GoogleSheetUpdateMany",
|
||||
"type": "object",
|
||||
"required": ["update", "filter"],
|
||||
"properties": {
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"description": "A MongoDB query expression to filter the data. All rows matched by the filter will be updated.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateMany request property \"filter\" should be an object."
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"type": "object",
|
||||
"description": "The update to apply to the row. An object where keys are the column names and values are the updated values.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateMany request property \"update\" should be an object."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"description": "The maximum number of rows to fetch.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateMany request property \"options.limit\" should be a number."
|
||||
}
|
||||
},
|
||||
"raw": {
|
||||
"type": "boolean",
|
||||
"description": "Store raw values instead of converting as if typed into the sheets UI.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateMany request property \"options.raw\" should be a boolean."
|
||||
}
|
||||
},
|
||||
"skip": {
|
||||
"type": "number",
|
||||
"description": "The number of rows to skip from the top of the sheet.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateMany request property \"options.skip\" should be a number."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateMany request properties should be an object.",
|
||||
"required": {
|
||||
"filter": "GoogleSheetUpdateMany request should have required property \"filter\".",
|
||||
"update": "GoogleSheetUpdateMany request should have required property \"update\"."
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import schema from './GoogleSheetUpdateOneSchema.json';
|
||||
import cleanRows from '../cleanRows';
|
||||
import getSheet from '../getSheet';
|
||||
import { transformRead, transformWrite } from '../transformTypes';
|
||||
import mingoFilter from '../mingoFilter';
|
||||
|
||||
async function googleSheetUpdateOne({ request, connection }) {
|
||||
const { filter, update, options = {} } = request;
|
||||
const { limit, skip, upsert, raw } = options;
|
||||
const sheet = await getSheet({ connection });
|
||||
let rows = await sheet.getRows({ limit, offset: skip });
|
||||
rows = transformRead({ input: rows, types: connection.columnTypes });
|
||||
rows = mingoFilter({ input: rows, filter });
|
||||
const transformedUpdate = transformWrite({ input: update, types: connection.columnTypes });
|
||||
if (rows.length === 0) {
|
||||
if (upsert) {
|
||||
const insertedRow = await sheet.addRow(transformedUpdate, { raw });
|
||||
return {
|
||||
modifiedCount: 1,
|
||||
upserted: true,
|
||||
row: cleanRows(insertedRow),
|
||||
};
|
||||
}
|
||||
return {
|
||||
modifiedCount: 0,
|
||||
upserted: false,
|
||||
};
|
||||
}
|
||||
const row = rows[0];
|
||||
Object.assign(row, transformedUpdate);
|
||||
await row.save({ raw });
|
||||
return {
|
||||
modifiedCount: 1,
|
||||
upserted: false,
|
||||
row: cleanRows(row),
|
||||
};
|
||||
}
|
||||
|
||||
export default { resolver: googleSheetUpdateOne, schema, checkRead: false, checkWrite: true };
|
@ -0,0 +1,327 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { validate } from '@lowdefy/ajv';
|
||||
import GoogleSheetUpdateOne from './GoogleSheetUpdateOne';
|
||||
|
||||
const mockGetRows = jest.fn();
|
||||
const mockAddRow = jest.fn();
|
||||
|
||||
const mockSave = jest.fn();
|
||||
jest.mock('../getSheet', () => () => ({
|
||||
addRow: mockAddRow,
|
||||
getRows: mockGetRows,
|
||||
}));
|
||||
|
||||
const { resolver, schema, checkRead, checkWrite } = GoogleSheetUpdateOne;
|
||||
|
||||
const mockAddRowDefaultImp = (row) => ({ ...row, _sheet: {} });
|
||||
const mockGetRowsDefaultImp = ({ limit, offset }) => {
|
||||
const rows = [
|
||||
{
|
||||
_rowNumber: 2,
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
id: '1',
|
||||
name: 'John',
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
save: mockSave,
|
||||
},
|
||||
{
|
||||
_rowNumber: 3,
|
||||
_rawData: ['2', 'Steven', '43', '2020/04/27', 'FALSE'],
|
||||
id: '2',
|
||||
name: 'Steve',
|
||||
age: '43',
|
||||
birth_date: '2020/04/27',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
save: mockSave,
|
||||
},
|
||||
{
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'Tim',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
_sheet: {},
|
||||
save: mockSave,
|
||||
},
|
||||
{
|
||||
_rowNumber: 5,
|
||||
_rawData: ['4', 'Craig', '21', '2020-04-25T22:00:00.000Z', 'TRUE'],
|
||||
id: '4',
|
||||
name: 'Craig',
|
||||
age: '120',
|
||||
birth_date: '2020-04-25T22:00:00.000Z',
|
||||
married: 'TRUE',
|
||||
_sheet: {},
|
||||
save: mockSave,
|
||||
},
|
||||
];
|
||||
return Promise.resolve(rows.slice(offset).slice(undefined, limit));
|
||||
};
|
||||
|
||||
test('googleSheetUpdateOne, match one', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: '1' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 1,
|
||||
row: {
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
_rowNumber: 2,
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
id: '1',
|
||||
married: 'TRUE',
|
||||
name: 'New',
|
||||
save: mockSave,
|
||||
},
|
||||
upserted: false,
|
||||
});
|
||||
expect(mockSave.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetUpdateOne, match one, raw true', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: '1' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
options: {
|
||||
raw: true,
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 1,
|
||||
row: {
|
||||
_rawData: ['1', 'John', '34', '2020/04/26', 'TRUE'],
|
||||
_rowNumber: 2,
|
||||
age: '34',
|
||||
birth_date: '2020/04/26',
|
||||
id: '1',
|
||||
married: 'TRUE',
|
||||
name: 'New',
|
||||
save: mockSave,
|
||||
},
|
||||
upserted: false,
|
||||
});
|
||||
expect(mockSave.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"raw": true,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetUpdateOne, match nothing', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: 'does_not_exist' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 0,
|
||||
upserted: false,
|
||||
});
|
||||
expect(mockSave.mock.calls).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
|
||||
test('googleSheetUpdateOne, match nothing, upsert true', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
mockAddRow.mockImplementation(mockAddRowDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { id: 'does_not_exist' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
options: {
|
||||
upsert: true,
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 1,
|
||||
row: {
|
||||
name: 'New',
|
||||
},
|
||||
upserted: true,
|
||||
});
|
||||
expect(mockAddRow.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"name": "New",
|
||||
},
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('googleSheetUpdateOne, match more than one', async () => {
|
||||
mockGetRows.mockImplementation(mockGetRowsDefaultImp);
|
||||
const res = await resolver({
|
||||
request: {
|
||||
filter: { _rowNumber: { $gt: 3 } },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
},
|
||||
connection: {},
|
||||
});
|
||||
expect(res).toEqual({
|
||||
modifiedCount: 1,
|
||||
row: {
|
||||
_rowNumber: 4,
|
||||
_rawData: ['3', 'Tim', '34', '2020/04/28', 'FALSE'],
|
||||
id: '3',
|
||||
name: 'New',
|
||||
age: '34',
|
||||
birth_date: '2020/04/28',
|
||||
married: 'FALSE',
|
||||
save: mockSave,
|
||||
},
|
||||
upserted: false,
|
||||
});
|
||||
expect(mockSave.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"raw": undefined,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('valid request schema', () => {
|
||||
const request = {
|
||||
filter: { id: '1' },
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
};
|
||||
expect(validate({ schema, data: request })).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('request properties is not an object', () => {
|
||||
const request = 'request';
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateOne request properties should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('filter is not an object', () => {
|
||||
const request = {
|
||||
filter: 'filter',
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateOne request property "filter" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('update is not an object', () => {
|
||||
const request = {
|
||||
filter: { id: '1' },
|
||||
update: 'update',
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateOne request property "update" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('filter is missing', () => {
|
||||
const request = {
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateOne request should have required property "filter".'
|
||||
);
|
||||
});
|
||||
|
||||
test('update is missing', () => {
|
||||
const request = {
|
||||
filter: { id: '1' },
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateOne request should have required property "update".'
|
||||
);
|
||||
});
|
||||
|
||||
test('options.raw is not a boolean', () => {
|
||||
const request = {
|
||||
filter: 'filter',
|
||||
update: {
|
||||
name: 'New',
|
||||
},
|
||||
options: {
|
||||
raw: 'raw',
|
||||
},
|
||||
};
|
||||
expect(() => validate({ schema, data: request })).toThrow(
|
||||
'GoogleSheetUpdateOne request property "options.raw" should be a boolean.'
|
||||
);
|
||||
});
|
||||
|
||||
test('checkRead should be false', async () => {
|
||||
expect(checkRead).toBe(false);
|
||||
});
|
||||
|
||||
test('checkWrite should be true', async () => {
|
||||
expect(checkWrite).toBe(true);
|
||||
});
|
@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Lowdefy Request Schema - GoogleSheetUpdateOne",
|
||||
"type": "object",
|
||||
"required": ["update", "filter"],
|
||||
"properties": {
|
||||
"filter": {
|
||||
"type": "object",
|
||||
"description": "A MongoDB query expression to filter the data. The first row matched by the filter will be updated.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateOne request property \"filter\" should be an object."
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"type": "object",
|
||||
"description": "The update to apply to the row. An object where keys are the column names and values are the updated values.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateOne request property \"update\" should be an object."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"description": "The maximum number of rows to fetch.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateOne request property \"options.limit\" should be a number."
|
||||
}
|
||||
},
|
||||
"raw": {
|
||||
"type": "boolean",
|
||||
"description": "Store raw values instead of converting as if typed into the sheets UI.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateOne request property \"options.raw\" should be a boolean."
|
||||
}
|
||||
},
|
||||
"skip": {
|
||||
"type": "number",
|
||||
"description": "The number of rows to skip from the top of the sheet.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateOne request property \"options.skip\" should be a number."
|
||||
}
|
||||
},
|
||||
"upsert": {
|
||||
"type": "boolean",
|
||||
"description": "Insert the row if no rows are matched by the filter.",
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateOne request property \"options.upsert\" should be a boolean."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "GoogleSheetUpdateOne request properties should be an object.",
|
||||
"required": {
|
||||
"filter": "GoogleSheetUpdateOne request should have required property \"filter\".",
|
||||
"update": "GoogleSheetUpdateOne request should have required property \"update\"."
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { type } from '@lowdefy/helpers';
|
||||
|
||||
function cleanRow(row) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { _sheet, ...rest } = row;
|
||||
return { ...rest };
|
||||
}
|
||||
|
||||
function cleanRows(input) {
|
||||
if (type.isObject(input)) {
|
||||
return cleanRow(input);
|
||||
}
|
||||
if (type.isArray(input)) {
|
||||
return input.map((row) => cleanRow(row));
|
||||
}
|
||||
throw new Error(`cleanRows received invalid input type ${type.typeOf(input)}.`);
|
||||
}
|
||||
|
||||
export default cleanRows;
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import cleanRows from './cleanRows';
|
||||
|
||||
test('cleanRows removes objects with key _sheet from an array of rows', () => {
|
||||
expect(
|
||||
cleanRows([
|
||||
{
|
||||
id: 1,
|
||||
value: 'a',
|
||||
_sheet: {
|
||||
string: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: 'b',
|
||||
_sheet: {
|
||||
string: 'string',
|
||||
},
|
||||
},
|
||||
])
|
||||
).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
value: 'a',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: 'b',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('cleanRows removes objects with key _sheet from a row', () => {
|
||||
expect(
|
||||
cleanRows({
|
||||
id: 1,
|
||||
value: 'a',
|
||||
_sheet: {
|
||||
string: 'string',
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
id: 1,
|
||||
value: 'a',
|
||||
});
|
||||
});
|
||||
|
||||
test('cleanRows invalid input', () => {
|
||||
expect(() => cleanRows(1)).toThrow('cleanRows received invalid input type number.');
|
||||
});
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { GoogleSpreadsheet } from 'google-spreadsheet';
|
||||
|
||||
async function authenticate({ doc, apiKey, client_email, private_key }) {
|
||||
if (apiKey) {
|
||||
doc.useApiKey(apiKey);
|
||||
} else {
|
||||
await doc.useServiceAccountAuth({
|
||||
client_email: client_email,
|
||||
private_key: private_key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSheetFromDoc({ doc, sheetId, sheetIndex }) {
|
||||
let sheet;
|
||||
if (sheetId) {
|
||||
sheet = doc.sheetsById[sheetId];
|
||||
if (!sheet) {
|
||||
throw new Error(`Could not find sheet with sheetId "${sheetId}"`);
|
||||
}
|
||||
} else {
|
||||
sheet = doc.sheetsByIndex[sheetIndex];
|
||||
if (!sheet) {
|
||||
throw new Error(`Could not find sheet with sheetIndex ${sheetIndex}`);
|
||||
}
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
|
||||
async function getSheet({ connection }) {
|
||||
const { apiKey, client_email, private_key, sheetId, sheetIndex, spreadsheetId } = connection;
|
||||
const doc = new GoogleSpreadsheet(spreadsheetId);
|
||||
|
||||
await authenticate({ doc, apiKey, client_email, private_key });
|
||||
|
||||
await doc.loadInfo();
|
||||
|
||||
return getSheetFromDoc({ doc, sheetId, sheetIndex });
|
||||
}
|
||||
|
||||
export default getSheet;
|
@ -0,0 +1,239 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { GoogleSpreadsheet } from 'google-spreadsheet';
|
||||
import getSheet from './getSheet';
|
||||
|
||||
// Not testing if spreadsheetId is given to GoogleSpreadsheet class in
|
||||
// const doc = new GoogleSpreadsheet(spreadsheetId);
|
||||
|
||||
const mockSheetsById = {};
|
||||
const mockSheetsByIndex = {};
|
||||
const mockUseApiKey = jest.fn();
|
||||
const mockUseServiceAccountAuth = jest.fn();
|
||||
const mockLoadInfo = jest.fn();
|
||||
|
||||
jest.mock('google-spreadsheet', () => {
|
||||
function GoogleSpreadsheet() {
|
||||
return {
|
||||
sheetsById: mockSheetsById,
|
||||
sheetsByIndex: mockSheetsByIndex,
|
||||
useApiKey: mockUseApiKey,
|
||||
useServiceAccountAuth: mockUseServiceAccountAuth,
|
||||
loadInfo: mockLoadInfo,
|
||||
};
|
||||
}
|
||||
return {
|
||||
GoogleSpreadsheet,
|
||||
};
|
||||
});
|
||||
|
||||
async function wait(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
const useApiKeyDefaultImp = (apiKey) => {
|
||||
if (apiKey !== 'valid') {
|
||||
throw new Error('Test Api Key Auth Error.');
|
||||
}
|
||||
};
|
||||
|
||||
const useServiceAccountAuthDefaultImp = async ({ client_email, private_key }) => {
|
||||
await wait(3);
|
||||
if (client_email !== 'client_email' || private_key !== 'private_key') {
|
||||
throw new Error('Test Service Account Auth Error.');
|
||||
}
|
||||
};
|
||||
|
||||
const loadInfoDefaultImp = async () => {
|
||||
await wait(3);
|
||||
mockSheetsById.sheetId1 = { id: 'sheetId1' };
|
||||
mockSheetsByIndex[0] = { index: 0 };
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
Object.keys(mockSheetsById).forEach((key) => {
|
||||
delete mockSheetsById[key];
|
||||
});
|
||||
Object.keys(mockSheetsByIndex).forEach((key) => {
|
||||
delete mockSheetsByIndex[key];
|
||||
});
|
||||
});
|
||||
|
||||
test('getSheet with apiKey, sheetId', async () => {
|
||||
mockUseApiKey.mockImplementation(useApiKeyDefaultImp);
|
||||
mockUseServiceAccountAuth.mockImplementation(useServiceAccountAuthDefaultImp);
|
||||
mockLoadInfo.mockImplementation(loadInfoDefaultImp);
|
||||
const sheet = await getSheet({
|
||||
connection: {
|
||||
apiKey: 'valid',
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetId: 'sheetId1',
|
||||
},
|
||||
});
|
||||
expect(mockUseApiKey.mock.calls).toEqual([['valid']]);
|
||||
expect(mockUseServiceAccountAuth.mock.calls).toEqual([]);
|
||||
expect(mockLoadInfo.mock.calls).toEqual([[]]);
|
||||
expect(sheet).toEqual({ id: 'sheetId1' });
|
||||
});
|
||||
|
||||
test('getSheet with service account, sheetId', async () => {
|
||||
mockUseApiKey.mockImplementation(useApiKeyDefaultImp);
|
||||
mockUseServiceAccountAuth.mockImplementation(useServiceAccountAuthDefaultImp);
|
||||
mockLoadInfo.mockImplementation(loadInfoDefaultImp);
|
||||
const sheet = await getSheet({
|
||||
connection: {
|
||||
client_email: 'client_email',
|
||||
private_key: 'private_key',
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetId: 'sheetId1',
|
||||
},
|
||||
});
|
||||
expect(mockUseApiKey.mock.calls).toEqual([]);
|
||||
expect(mockUseServiceAccountAuth.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
client_email: 'client_email',
|
||||
private_key: 'private_key',
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockLoadInfo.mock.calls).toEqual([[]]);
|
||||
expect(sheet).toEqual({ id: 'sheetId1' });
|
||||
});
|
||||
|
||||
test('getSheet with service account, sheetIndex', async () => {
|
||||
mockUseApiKey.mockImplementation(useApiKeyDefaultImp);
|
||||
mockUseServiceAccountAuth.mockImplementation(useServiceAccountAuthDefaultImp);
|
||||
mockLoadInfo.mockImplementation(loadInfoDefaultImp);
|
||||
const sheet = await getSheet({
|
||||
connection: {
|
||||
client_email: 'client_email',
|
||||
private_key: 'private_key',
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetIndex: 0,
|
||||
},
|
||||
});
|
||||
expect(mockUseApiKey.mock.calls).toEqual([]);
|
||||
expect(mockUseServiceAccountAuth.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
client_email: 'client_email',
|
||||
private_key: 'private_key',
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockLoadInfo.mock.calls).toEqual([[]]);
|
||||
expect(sheet).toEqual({ index: 0 });
|
||||
});
|
||||
|
||||
test('getSheet with invalid apiKey', async () => {
|
||||
mockUseApiKey.mockImplementation(useApiKeyDefaultImp);
|
||||
mockUseServiceAccountAuth.mockImplementation(useServiceAccountAuthDefaultImp);
|
||||
mockLoadInfo.mockImplementation(loadInfoDefaultImp);
|
||||
await expect(() =>
|
||||
getSheet({
|
||||
connection: {
|
||||
apiKey: 'invalid',
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetId: 'sheetId1',
|
||||
},
|
||||
})
|
||||
).rejects.toThrow('Test Api Key Auth Error.');
|
||||
expect(mockUseApiKey.mock.calls).toEqual([['invalid']]);
|
||||
});
|
||||
|
||||
test('getSheet with invalid client_id', async () => {
|
||||
mockUseApiKey.mockImplementation(useApiKeyDefaultImp);
|
||||
mockUseServiceAccountAuth.mockImplementation(useServiceAccountAuthDefaultImp);
|
||||
mockLoadInfo.mockImplementation(loadInfoDefaultImp);
|
||||
await expect(() =>
|
||||
getSheet({
|
||||
connection: {
|
||||
client_email: 'invalid_client_email',
|
||||
private_key: 'private_key',
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetId: 'sheetId1',
|
||||
},
|
||||
})
|
||||
).rejects.toThrow('Test Service Account Auth Error.');
|
||||
expect(mockUseServiceAccountAuth.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
client_email: 'invalid_client_email',
|
||||
private_key: 'private_key',
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('getSheet with invalid private_key', async () => {
|
||||
mockUseApiKey.mockImplementation(useApiKeyDefaultImp);
|
||||
mockUseServiceAccountAuth.mockImplementation(useServiceAccountAuthDefaultImp);
|
||||
mockLoadInfo.mockImplementation(loadInfoDefaultImp);
|
||||
await expect(() =>
|
||||
getSheet({
|
||||
connection: {
|
||||
client_email: 'client_email',
|
||||
private_key: 'invalid_private_key',
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetId: 'sheetId1',
|
||||
},
|
||||
})
|
||||
).rejects.toThrow('Test Service Account Auth Error.');
|
||||
expect(mockUseServiceAccountAuth.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
client_email: 'client_email',
|
||||
private_key: 'invalid_private_key',
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('getSheet with sheetId, sheet does not exist', async () => {
|
||||
mockUseApiKey.mockImplementation(useApiKeyDefaultImp);
|
||||
mockUseServiceAccountAuth.mockImplementation(useServiceAccountAuthDefaultImp);
|
||||
mockLoadInfo.mockImplementation(loadInfoDefaultImp);
|
||||
await expect(
|
||||
getSheet({
|
||||
connection: {
|
||||
apiKey: 'valid',
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetId: 'sheetId2',
|
||||
},
|
||||
})
|
||||
).rejects.toThrow('Could not find sheet with sheetId "sheetId2"');
|
||||
});
|
||||
|
||||
test('getSheet with sheetIndex, sheet does not exist', async () => {
|
||||
mockUseApiKey.mockImplementation(useApiKeyDefaultImp);
|
||||
mockUseServiceAccountAuth.mockImplementation(useServiceAccountAuthDefaultImp);
|
||||
mockLoadInfo.mockImplementation(loadInfoDefaultImp);
|
||||
await expect(
|
||||
getSheet({
|
||||
connection: {
|
||||
apiKey: 'valid',
|
||||
spreadsheetId: 'spreadsheetId',
|
||||
sheetIndex: 1,
|
||||
},
|
||||
})
|
||||
).rejects.toThrow('Could not find sheet with sheetIndex 1');
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { type } from '@lowdefy/helpers';
|
||||
import mingo from 'mingo';
|
||||
import { useOperators, OperatorType } from 'mingo/core';
|
||||
import * as accumulatorOperators from 'mingo/operators/accumulator';
|
||||
import * as expressionOperators from 'mingo/operators/expression';
|
||||
import * as pipelineOperators from 'mingo/operators/pipeline';
|
||||
import * as queryOperators from 'mingo/operators/query';
|
||||
import * as projectionOperators from 'mingo/operators/projection';
|
||||
|
||||
useOperators(OperatorType.ACCUMULATOR, accumulatorOperators);
|
||||
useOperators(OperatorType.EXPRESSION, expressionOperators);
|
||||
useOperators(OperatorType.PIPELINE, pipelineOperators);
|
||||
useOperators(OperatorType.PROJECTION, queryOperators);
|
||||
useOperators(OperatorType.QUERY, projectionOperators);
|
||||
|
||||
function mingoAggregation({ input = [], pipeline = [] }) {
|
||||
if (!type.isArray(input)) {
|
||||
throw new Error('Mingo aggregation error. Argument "input" should be an array.');
|
||||
}
|
||||
if (!type.isArray(pipeline)) {
|
||||
throw new Error('Mingo aggregation error. Argument "pipeline" should be an array.');
|
||||
}
|
||||
const aggregator = new mingo.Aggregator(pipeline);
|
||||
return aggregator.run(input);
|
||||
}
|
||||
|
||||
export default mingoAggregation;
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import mingoAggregation from './mingoAggregation';
|
||||
|
||||
test('mingoAggregation sort', () => {
|
||||
const pipeline = [
|
||||
{
|
||||
$sort: {
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
const input = [
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
];
|
||||
const res = mingoAggregation({ input, pipeline });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoAggregation group', () => {
|
||||
const pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: 0,
|
||||
count: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
];
|
||||
const input = [
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
];
|
||||
const res = mingoAggregation({ input, pipeline });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
_id: 0,
|
||||
count: 2,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoAggregation empty pipeline', () => {
|
||||
const pipeline = [];
|
||||
const input = [
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
];
|
||||
const res = mingoAggregation({ input, pipeline });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoAggregation undefined pipeline', () => {
|
||||
const input = [
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
];
|
||||
const res = mingoAggregation({ input });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoAggregation empty input', () => {
|
||||
const pipeline = [
|
||||
{
|
||||
$sort: {
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
const input = [];
|
||||
const res = mingoAggregation({ input, pipeline });
|
||||
expect(res).toEqual([]);
|
||||
});
|
||||
|
||||
test('mingoAggregation undefined input', () => {
|
||||
const pipeline = [
|
||||
{
|
||||
$sort: {
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
const res = mingoAggregation({ pipeline });
|
||||
expect(res).toEqual([]);
|
||||
});
|
||||
|
||||
test('mingoAggregation pipeline is not an array', () => {
|
||||
const pipeline = 'pipeline';
|
||||
const input = [
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
];
|
||||
expect(() => mingoAggregation({ input, pipeline })).toThrow(
|
||||
'Mingo aggregation error. Argument "pipeline" should be an array.'
|
||||
);
|
||||
});
|
||||
|
||||
test('mingoAggregation input is not an array', () => {
|
||||
const pipeline = [
|
||||
{
|
||||
$sort: {
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
const input = 'input';
|
||||
expect(() => mingoAggregation({ input, pipeline })).toThrow(
|
||||
'Mingo aggregation error. Argument "input" should be an array.'
|
||||
);
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { type } from '@lowdefy/helpers';
|
||||
import mingoAggregation from './mingoAggregation';
|
||||
|
||||
function mingoFilter({ input = [], filter = {} }) {
|
||||
if (!type.isObject(filter)) {
|
||||
throw new Error('Mingo filter error. Argument "filter" should be an object.');
|
||||
}
|
||||
if (!type.isArray(input)) {
|
||||
throw new Error('Mingo filter error. Argument "input" should be an array.');
|
||||
}
|
||||
const pipeline = [{ $match: filter }];
|
||||
return mingoAggregation({ input, pipeline });
|
||||
}
|
||||
|
||||
export default mingoFilter;
|
@ -0,0 +1,207 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import mingoFilter from './mingoFilter';
|
||||
|
||||
test('mingoFilter equals shorthand', () => {
|
||||
const filter = { id: 1 };
|
||||
const input = [
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
];
|
||||
const res = mingoFilter({ input, filter });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoFilter greater than', () => {
|
||||
const filter = { id: { $gt: 1 } };
|
||||
const input = [
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
];
|
||||
const res = mingoFilter({ input, filter });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoFilter $in', () => {
|
||||
const filter = { id: { $in: [1, 3] } };
|
||||
const input = [
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
];
|
||||
const res = mingoFilter({ input, filter });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoFilter filter empty object', () => {
|
||||
const filter = {};
|
||||
const input = [
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
];
|
||||
const res = mingoFilter({ input, filter });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoFilter filter undefined', () => {
|
||||
const input = [
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
];
|
||||
const res = mingoFilter({ input });
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('mingoFilter input empty array', () => {
|
||||
const filter = { id: 1 };
|
||||
const input = [];
|
||||
const res = mingoFilter({ input, filter });
|
||||
expect(res).toEqual([]);
|
||||
});
|
||||
|
||||
test('mingoFilter input undefined', () => {
|
||||
const filter = { id: 1 };
|
||||
const res = mingoFilter({ filter });
|
||||
expect(res).toEqual([]);
|
||||
});
|
||||
|
||||
test('mingoFilter filter not an object', () => {
|
||||
const filter = 'filter';
|
||||
const input = [
|
||||
{
|
||||
id: 1,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
x: 'x',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
x: 'x',
|
||||
},
|
||||
];
|
||||
expect(() => mingoFilter({ input, filter })).toThrow(
|
||||
'Mingo filter error. Argument "filter" should be an object.'
|
||||
);
|
||||
});
|
||||
|
||||
test('mingoFilter input not an array', () => {
|
||||
const filter = { id: 1 };
|
||||
const input = 'input';
|
||||
expect(() => mingoFilter({ input, filter })).toThrow(
|
||||
'Mingo filter error. Argument "input" should be an array.'
|
||||
);
|
||||
});
|
@ -0,0 +1,677 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { transformRead } from './transformTypes';
|
||||
|
||||
test('transformRead invalid input', () => {
|
||||
expect(() => transformRead({ input: 1 })).toThrow(
|
||||
'transformRead received invalid input type number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('transformRead works on an object', () => {
|
||||
expect(
|
||||
transformRead({
|
||||
input: {
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
stringTransform: 'string',
|
||||
numberTransform: '1',
|
||||
booleanTransform: 'TRUE',
|
||||
dateTransform: '2020/01/26',
|
||||
jsonTransform: '{"key":"value"}',
|
||||
},
|
||||
types: {
|
||||
stringTransform: 'string',
|
||||
numberTransform: 'number',
|
||||
booleanTransform: 'boolean',
|
||||
dateTransform: 'date',
|
||||
jsonTransform: 'json',
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
stringTransform: 'string',
|
||||
numberTransform: 1,
|
||||
booleanTransform: true,
|
||||
dateTransform: new Date('2020-01-26T00:00:00.000Z'),
|
||||
jsonTransform: { key: 'value' },
|
||||
});
|
||||
});
|
||||
|
||||
test('transformRead works on an object, no types provided', () => {
|
||||
expect(
|
||||
transformRead({
|
||||
input: {
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
});
|
||||
});
|
||||
|
||||
test('transformRead works on an array', () => {
|
||||
expect(
|
||||
transformRead({
|
||||
input: [
|
||||
{
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
stringTransform: 'string',
|
||||
numberTransform: '1',
|
||||
booleanTransform: 'TRUE',
|
||||
dateTransform: '2020/01/26',
|
||||
jsonTransform: '{"key":"value"}',
|
||||
},
|
||||
{
|
||||
string: 'string2',
|
||||
number: '2',
|
||||
boolean: 'FALSE',
|
||||
date: '2020/01/27',
|
||||
json: '{"key":"value2"}',
|
||||
stringTransform: 'string2',
|
||||
numberTransform: '2',
|
||||
booleanTransform: 'FALSE',
|
||||
dateTransform: '2020/01/27',
|
||||
jsonTransform: '{"key":"value2"}',
|
||||
},
|
||||
],
|
||||
types: {
|
||||
stringTransform: 'string',
|
||||
numberTransform: 'number',
|
||||
booleanTransform: 'boolean',
|
||||
dateTransform: 'date',
|
||||
jsonTransform: 'json',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
stringTransform: 'string',
|
||||
numberTransform: 1,
|
||||
booleanTransform: true,
|
||||
dateTransform: new Date('2020-01-26T00:00:00.000Z'),
|
||||
jsonTransform: { key: 'value' },
|
||||
},
|
||||
{
|
||||
string: 'string2',
|
||||
number: '2',
|
||||
boolean: 'FALSE',
|
||||
date: '2020/01/27',
|
||||
json: '{"key":"value2"}',
|
||||
stringTransform: 'string2',
|
||||
numberTransform: 2,
|
||||
booleanTransform: false,
|
||||
dateTransform: new Date('2020-01-27T00:00:00.000Z'),
|
||||
jsonTransform: { key: 'value2' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformRead works on an array, no types provided', () => {
|
||||
expect(
|
||||
transformRead({
|
||||
input: [
|
||||
{
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
},
|
||||
{
|
||||
string: 'string2',
|
||||
number: '2',
|
||||
boolean: 'FALSE',
|
||||
date: '2020/01/27',
|
||||
json: '{"key":"value2"}',
|
||||
},
|
||||
],
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
},
|
||||
{
|
||||
string: 'string2',
|
||||
number: '2',
|
||||
boolean: 'FALSE',
|
||||
date: '2020/01/27',
|
||||
json: '{"key":"value2"}',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformRead numbers', () => {
|
||||
expect(
|
||||
transformRead({
|
||||
input: [
|
||||
{
|
||||
numberTransform: '1',
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
numberTransform: '2.0',
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
numberTransform: '3.141592',
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
numberTransform: '4.2asdfg',
|
||||
original: '4.2asdfg',
|
||||
},
|
||||
{
|
||||
numberTransform: NaN,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
numberTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
numberTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
numberTransform: {},
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
numberTransform: [],
|
||||
original: [],
|
||||
},
|
||||
],
|
||||
types: {
|
||||
numberTransform: 'number',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
numberTransform: 1,
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
numberTransform: 2,
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
numberTransform: 3.141592,
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
numberTransform: null,
|
||||
original: '4.2asdfg',
|
||||
},
|
||||
{
|
||||
numberTransform: null,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
numberTransform: 0,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
numberTransform: null,
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
numberTransform: null,
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
numberTransform: 0,
|
||||
original: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformRead booleans', () => {
|
||||
expect(
|
||||
transformRead({
|
||||
input: [
|
||||
{
|
||||
booleanTransform: 'TRUE',
|
||||
original: 'TRUE',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'FALSE',
|
||||
original: 'FALSE',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'False',
|
||||
original: 'False',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'True',
|
||||
original: 'True',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'true',
|
||||
original: 'true',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'false',
|
||||
original: 'false',
|
||||
},
|
||||
{
|
||||
booleanTransform: true,
|
||||
original: true,
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: false,
|
||||
},
|
||||
{
|
||||
booleanTransform: 1,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
booleanTransform: 0,
|
||||
original: 0,
|
||||
},
|
||||
{
|
||||
booleanTransform: 42,
|
||||
original: 42,
|
||||
},
|
||||
{
|
||||
booleanTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
booleanTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
booleanTransform: {},
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
booleanTransform: [],
|
||||
original: [],
|
||||
},
|
||||
],
|
||||
types: {
|
||||
booleanTransform: 'boolean',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
booleanTransform: true,
|
||||
original: 'TRUE',
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 'FALSE',
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 'False',
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 'True',
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 'true',
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 'false',
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: true,
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: false,
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 0,
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 42,
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformRead dates', () => {
|
||||
expect(
|
||||
transformRead({
|
||||
input: [
|
||||
{
|
||||
dateTransform: '2020/01/27',
|
||||
original: '2020/01/27',
|
||||
},
|
||||
{
|
||||
dateTransform: '2020-01-27T00:00:00.000Z',
|
||||
original: '2020-01-27T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
dateTransform: 1,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
dateTransform: '1',
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
dateTransform: '2.0',
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
dateTransform: '3.141592',
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
dateTransform: NaN,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
dateTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
dateTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
],
|
||||
types: {
|
||||
dateTransform: 'date',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
dateTransform: new Date('2020-01-27T00:00:00.000Z'),
|
||||
original: '2020/01/27',
|
||||
},
|
||||
{
|
||||
dateTransform: new Date('2020-01-27T00:00:00.000Z'),
|
||||
original: '2020-01-27T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
dateTransform: new Date('1970-01-01T00:00:00.001Z'),
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
dateTransform: new Date('2001-01-01T00:00:00.000Z'), // This is weird
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
dateTransform: null,
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
dateTransform: null,
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
dateTransform: null,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
dateTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
dateTransform: null,
|
||||
original: 'Hello',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformRead json', () => {
|
||||
expect(
|
||||
transformRead({
|
||||
input: [
|
||||
{
|
||||
jsonTransform: '1',
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
jsonTransform: '2.0',
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
jsonTransform: '3.141592',
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
jsonTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
jsonTransform: '"Hello"',
|
||||
original: '"Hello"',
|
||||
},
|
||||
{
|
||||
jsonTransform: 'null',
|
||||
original: 'null',
|
||||
},
|
||||
{
|
||||
jsonTransform: 'true',
|
||||
original: 'true',
|
||||
},
|
||||
{
|
||||
jsonTransform: 'false',
|
||||
original: 'false',
|
||||
},
|
||||
{
|
||||
jsonTransform: '{"key":"value"}',
|
||||
original: '{"key":"value"}',
|
||||
},
|
||||
{
|
||||
jsonTransform: '[1,2,3]',
|
||||
original: '[1,2,3]',
|
||||
},
|
||||
{
|
||||
jsonTransform: '{}',
|
||||
original: '{}',
|
||||
},
|
||||
{
|
||||
jsonTransform: '[]',
|
||||
original: '[]',
|
||||
},
|
||||
{
|
||||
jsonTransform: '[1,2,3',
|
||||
original: '[1,2,3',
|
||||
},
|
||||
{
|
||||
jsonTransform: '"Hell',
|
||||
original: '"Hell',
|
||||
},
|
||||
{
|
||||
jsonTransform: 'undefined',
|
||||
original: 'undefined',
|
||||
},
|
||||
{
|
||||
jsonTransform: NaN,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
jsonTransform: true,
|
||||
original: true,
|
||||
},
|
||||
{
|
||||
jsonTransform: false,
|
||||
original: false,
|
||||
},
|
||||
{
|
||||
jsonTransform: 42,
|
||||
original: 42,
|
||||
},
|
||||
{
|
||||
jsonTransform: {},
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
jsonTransform: [],
|
||||
original: [],
|
||||
},
|
||||
],
|
||||
types: {
|
||||
jsonTransform: 'json',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
jsonTransform: 1,
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
jsonTransform: 2,
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
jsonTransform: 3.141592,
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
jsonTransform: 'Hello',
|
||||
original: '"Hello"',
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: 'null',
|
||||
},
|
||||
{
|
||||
jsonTransform: true,
|
||||
original: 'true',
|
||||
},
|
||||
{
|
||||
jsonTransform: false,
|
||||
original: 'false',
|
||||
},
|
||||
{
|
||||
jsonTransform: {
|
||||
key: 'value',
|
||||
},
|
||||
original: '{"key":"value"}',
|
||||
},
|
||||
{
|
||||
jsonTransform: [1, 2, 3],
|
||||
original: '[1,2,3]',
|
||||
},
|
||||
{
|
||||
jsonTransform: {},
|
||||
original: '{}',
|
||||
},
|
||||
{
|
||||
jsonTransform: [],
|
||||
original: '[]',
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: '[1,2,3',
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: '"Hell',
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: 'undefined',
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
jsonTransform: true,
|
||||
original: true,
|
||||
},
|
||||
{
|
||||
jsonTransform: false,
|
||||
original: false,
|
||||
},
|
||||
{
|
||||
jsonTransform: 42,
|
||||
original: 42,
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: [],
|
||||
},
|
||||
]);
|
||||
});
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { type } from '@lowdefy/helpers';
|
||||
import moment from 'moment';
|
||||
|
||||
const readTransformers = {
|
||||
string: (value) => value,
|
||||
number: (value) => {
|
||||
const number = Number(value);
|
||||
if (isNaN(number)) return null;
|
||||
return number;
|
||||
},
|
||||
boolean: (value) => value === 'TRUE',
|
||||
date: (value) => {
|
||||
const date = moment.utc(value);
|
||||
if (!date.isValid()) return null;
|
||||
return date.toDate();
|
||||
},
|
||||
json: (value) => {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const writeTransformers = {
|
||||
string: (value) => value,
|
||||
number: (value) => (type.isNumber(value) ? value.toString() : value),
|
||||
boolean: (value) => {
|
||||
if (value === true) return 'TRUE';
|
||||
if (value === false) return 'FALSE';
|
||||
return value;
|
||||
},
|
||||
date: (value) => (type.isDate(value) ? value.toISOString() : value),
|
||||
json: (value) => {
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch (_) {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const transformObject =
|
||||
({ transformers, types }) =>
|
||||
(object) => {
|
||||
Object.keys(object).forEach((key) => {
|
||||
if (types[key]) {
|
||||
object[key] = transformers[types[key]](object[key]);
|
||||
}
|
||||
});
|
||||
return object;
|
||||
};
|
||||
|
||||
function transformRead({ input, types = {} }) {
|
||||
if (type.isObject(input)) {
|
||||
return transformObject({ transformers: readTransformers, types })(input);
|
||||
}
|
||||
if (type.isArray(input)) {
|
||||
return input.map((obj) => transformObject({ transformers: readTransformers, types })(obj));
|
||||
}
|
||||
throw new Error(`transformRead received invalid input type ${type.typeOf(input)}.`);
|
||||
}
|
||||
|
||||
function transformWrite({ input, types = {} }) {
|
||||
if (type.isObject(input)) {
|
||||
return transformObject({ transformers: writeTransformers, types })(input);
|
||||
}
|
||||
if (type.isArray(input)) {
|
||||
return input.map((obj) => transformObject({ transformers: writeTransformers, types })(obj));
|
||||
}
|
||||
throw new Error(`transformWrite received invalid input type ${type.typeOf(input)}.`);
|
||||
}
|
||||
|
||||
export { transformRead, transformWrite };
|
@ -0,0 +1,654 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { transformWrite } from './transformTypes';
|
||||
|
||||
test('transformWrite invalid input', () => {
|
||||
expect(() => transformWrite({ input: 1 })).toThrow(
|
||||
'transformWrite received invalid input type number.'
|
||||
);
|
||||
});
|
||||
|
||||
test('transformWrite works on an object', () => {
|
||||
expect(
|
||||
transformWrite({
|
||||
input: {
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
stringTransform: 'string',
|
||||
numberTransform: 1,
|
||||
booleanTransform: true,
|
||||
dateTransform: new Date('2020-01-26T00:00:00.000Z'),
|
||||
jsonTransform: { key: 'value' },
|
||||
},
|
||||
types: {
|
||||
stringTransform: 'string',
|
||||
numberTransform: 'number',
|
||||
booleanTransform: 'boolean',
|
||||
dateTransform: 'date',
|
||||
jsonTransform: 'json',
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
|
||||
stringTransform: 'string',
|
||||
numberTransform: '1',
|
||||
booleanTransform: 'TRUE',
|
||||
dateTransform: '2020-01-26T00:00:00.000Z',
|
||||
jsonTransform: '{"key":"value"}',
|
||||
});
|
||||
});
|
||||
|
||||
test('transformWrite works on an object, no types provided', () => {
|
||||
expect(
|
||||
transformWrite({
|
||||
input: {
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
});
|
||||
});
|
||||
|
||||
test('transformWrite works on an array', () => {
|
||||
expect(
|
||||
transformWrite({
|
||||
input: [
|
||||
{
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
stringTransform: 'string',
|
||||
numberTransform: 1,
|
||||
booleanTransform: true,
|
||||
dateTransform: new Date('2020-01-26T00:00:00.000Z'),
|
||||
jsonTransform: { key: 'value' },
|
||||
},
|
||||
{
|
||||
string: 'string2',
|
||||
number: '2',
|
||||
boolean: 'FALSE',
|
||||
date: '2020/01/27',
|
||||
json: '{"key":"value2"}',
|
||||
stringTransform: 'string2',
|
||||
numberTransform: 2,
|
||||
booleanTransform: false,
|
||||
dateTransform: new Date('2020-01-27T00:00:00.000Z'),
|
||||
jsonTransform: { key: 'value2' },
|
||||
},
|
||||
],
|
||||
types: {
|
||||
stringTransform: 'string',
|
||||
numberTransform: 'number',
|
||||
booleanTransform: 'boolean',
|
||||
dateTransform: 'date',
|
||||
jsonTransform: 'json',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
stringTransform: 'string',
|
||||
numberTransform: '1',
|
||||
booleanTransform: 'TRUE',
|
||||
dateTransform: '2020-01-26T00:00:00.000Z',
|
||||
jsonTransform: '{"key":"value"}',
|
||||
},
|
||||
{
|
||||
string: 'string2',
|
||||
number: '2',
|
||||
boolean: 'FALSE',
|
||||
date: '2020/01/27',
|
||||
json: '{"key":"value2"}',
|
||||
|
||||
stringTransform: 'string2',
|
||||
numberTransform: '2',
|
||||
booleanTransform: 'FALSE',
|
||||
dateTransform: '2020-01-27T00:00:00.000Z',
|
||||
jsonTransform: '{"key":"value2"}',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformWrite works on an array, no types provided', () => {
|
||||
expect(
|
||||
transformWrite({
|
||||
input: [
|
||||
{
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
},
|
||||
{
|
||||
string: 'string2',
|
||||
number: '2',
|
||||
boolean: 'FALSE',
|
||||
date: '2020/01/27',
|
||||
json: '{"key":"value2"}',
|
||||
},
|
||||
],
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
string: 'string',
|
||||
number: '1',
|
||||
boolean: 'TRUE',
|
||||
date: '2020/01/26',
|
||||
json: '{"key":"value"}',
|
||||
},
|
||||
{
|
||||
string: 'string2',
|
||||
number: '2',
|
||||
boolean: 'FALSE',
|
||||
date: '2020/01/27',
|
||||
json: '{"key":"value2"}',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformWrite numbers', () => {
|
||||
expect(
|
||||
transformWrite({
|
||||
input: [
|
||||
{
|
||||
numberTransform: 1,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
numberTransform: 2.0,
|
||||
original: 2.0,
|
||||
},
|
||||
{
|
||||
numberTransform: 3.141592,
|
||||
original: 3.141592,
|
||||
},
|
||||
{
|
||||
numberTransform: '1',
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
numberTransform: '2.0',
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
numberTransform: '3.141592',
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
numberTransform: '4.2asdfg',
|
||||
original: '4.2asdfg',
|
||||
},
|
||||
{
|
||||
numberTransform: NaN,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
numberTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
numberTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
numberTransform: {},
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
numberTransform: [],
|
||||
original: [],
|
||||
},
|
||||
],
|
||||
types: {
|
||||
numberTransform: 'number',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
numberTransform: '1',
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
numberTransform: '2',
|
||||
original: 2.0,
|
||||
},
|
||||
{
|
||||
numberTransform: '3.141592',
|
||||
original: 3.141592,
|
||||
},
|
||||
{
|
||||
numberTransform: '1',
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
numberTransform: '2.0',
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
numberTransform: '3.141592',
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
numberTransform: '4.2asdfg',
|
||||
original: '4.2asdfg',
|
||||
},
|
||||
{
|
||||
numberTransform: NaN,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
numberTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
numberTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
numberTransform: {},
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
numberTransform: [],
|
||||
original: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformWrite booleans', () => {
|
||||
expect(
|
||||
transformWrite({
|
||||
input: [
|
||||
{
|
||||
booleanTransform: true,
|
||||
original: true,
|
||||
},
|
||||
{
|
||||
booleanTransform: false,
|
||||
original: false,
|
||||
},
|
||||
{
|
||||
booleanTransform: 'TRUE',
|
||||
original: 'TRUE',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'FALSE',
|
||||
original: 'FALSE',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'False',
|
||||
original: 'False',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'True',
|
||||
original: 'True',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'true',
|
||||
original: 'true',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'false',
|
||||
original: 'false',
|
||||
},
|
||||
{
|
||||
booleanTransform: 1,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
booleanTransform: 0,
|
||||
original: 0,
|
||||
},
|
||||
{
|
||||
booleanTransform: 42,
|
||||
original: 42,
|
||||
},
|
||||
{
|
||||
booleanTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
booleanTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
booleanTransform: {},
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
booleanTransform: [],
|
||||
original: [],
|
||||
},
|
||||
],
|
||||
types: {
|
||||
booleanTransform: 'boolean',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
booleanTransform: 'TRUE',
|
||||
original: true,
|
||||
},
|
||||
{
|
||||
booleanTransform: 'FALSE',
|
||||
original: false,
|
||||
},
|
||||
{
|
||||
booleanTransform: 'TRUE',
|
||||
original: 'TRUE',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'FALSE',
|
||||
original: 'FALSE',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'False',
|
||||
original: 'False',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'True',
|
||||
original: 'True',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'true',
|
||||
original: 'true',
|
||||
},
|
||||
{
|
||||
booleanTransform: 'false',
|
||||
original: 'false',
|
||||
},
|
||||
{
|
||||
booleanTransform: 1,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
booleanTransform: 0,
|
||||
original: 0,
|
||||
},
|
||||
{
|
||||
booleanTransform: 42,
|
||||
original: 42,
|
||||
},
|
||||
{
|
||||
booleanTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
booleanTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
booleanTransform: {},
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
booleanTransform: [],
|
||||
original: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformWrite dates', () => {
|
||||
expect(
|
||||
transformWrite({
|
||||
input: [
|
||||
{
|
||||
dateTransform: new Date('2020-01-27T00:00:00.000Z'),
|
||||
original: new Date('2020-01-27T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
dateTransform: '2020/01/27',
|
||||
original: '2020/01/27',
|
||||
},
|
||||
{
|
||||
dateTransform: '2020-01-27T00:00:00.000Z',
|
||||
original: '2020-01-27T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
dateTransform: 1,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
dateTransform: '1',
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
dateTransform: '2.0',
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
dateTransform: '3.141592',
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
dateTransform: NaN,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
dateTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
dateTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
],
|
||||
types: {
|
||||
dateTransform: 'date',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
dateTransform: '2020-01-27T00:00:00.000Z',
|
||||
original: new Date('2020-01-27T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
dateTransform: '2020/01/27',
|
||||
original: '2020/01/27',
|
||||
},
|
||||
{
|
||||
dateTransform: '2020-01-27T00:00:00.000Z',
|
||||
original: '2020-01-27T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
dateTransform: 1,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
dateTransform: '1',
|
||||
original: '1',
|
||||
},
|
||||
{
|
||||
dateTransform: '2.0',
|
||||
original: '2.0',
|
||||
},
|
||||
{
|
||||
dateTransform: '3.141592',
|
||||
original: '3.141592',
|
||||
},
|
||||
{
|
||||
dateTransform: NaN,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
dateTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
dateTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('transformWrite json', () => {
|
||||
const circular = {};
|
||||
circular.reference = circular;
|
||||
expect(
|
||||
transformWrite({
|
||||
input: [
|
||||
{
|
||||
jsonTransform: 1,
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
jsonTransform: 2,
|
||||
original: 2.0,
|
||||
},
|
||||
{
|
||||
jsonTransform: 3.141592,
|
||||
original: 3.141592,
|
||||
},
|
||||
{
|
||||
jsonTransform: 'Hello',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
jsonTransform: null,
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
jsonTransform: true,
|
||||
original: true,
|
||||
},
|
||||
{
|
||||
jsonTransform: false,
|
||||
original: false,
|
||||
},
|
||||
{
|
||||
jsonTransform: {
|
||||
key: 'value',
|
||||
},
|
||||
original: {
|
||||
key: 'value',
|
||||
},
|
||||
},
|
||||
{
|
||||
jsonTransform: [1, 2, 3],
|
||||
original: [1, 2, 3],
|
||||
},
|
||||
{
|
||||
jsonTransform: {},
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
jsonTransform: [],
|
||||
original: [],
|
||||
},
|
||||
{
|
||||
jsonTransform: undefined,
|
||||
original: undefined,
|
||||
},
|
||||
{
|
||||
jsonTransform: NaN,
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
// JSON stringify throws on circular references
|
||||
jsonTransform: circular,
|
||||
original: circular,
|
||||
},
|
||||
],
|
||||
types: {
|
||||
jsonTransform: 'json',
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
jsonTransform: '1',
|
||||
original: 1,
|
||||
},
|
||||
{
|
||||
jsonTransform: '2',
|
||||
original: 2.0,
|
||||
},
|
||||
{
|
||||
jsonTransform: '3.141592',
|
||||
original: 3.141592,
|
||||
},
|
||||
{
|
||||
jsonTransform: '"Hello"',
|
||||
original: 'Hello',
|
||||
},
|
||||
{
|
||||
jsonTransform: 'null',
|
||||
original: null,
|
||||
},
|
||||
{
|
||||
jsonTransform: 'true',
|
||||
original: true,
|
||||
},
|
||||
{
|
||||
jsonTransform: 'false',
|
||||
original: false,
|
||||
},
|
||||
{
|
||||
jsonTransform: '{"key":"value"}',
|
||||
original: {
|
||||
key: 'value',
|
||||
},
|
||||
},
|
||||
{
|
||||
jsonTransform: '[1,2,3]',
|
||||
original: [1, 2, 3],
|
||||
},
|
||||
{
|
||||
jsonTransform: '{}',
|
||||
original: {},
|
||||
},
|
||||
{
|
||||
jsonTransform: '[]',
|
||||
original: [],
|
||||
},
|
||||
{
|
||||
jsonTransform: undefined,
|
||||
original: undefined,
|
||||
},
|
||||
{
|
||||
jsonTransform: 'null',
|
||||
original: NaN,
|
||||
},
|
||||
{
|
||||
jsonTransform: circular,
|
||||
original: circular,
|
||||
},
|
||||
]);
|
||||
});
|
7
packages/connections/google-sheets/src/index.js
Normal file
7
packages/connections/google-sheets/src/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
import GoogleSheet from './GoogleSheet/GoogleSheet';
|
||||
|
||||
export const connections = {
|
||||
GoogleSheet,
|
||||
};
|
||||
|
||||
export default { connections };
|
19
yarn.lock
19
yarn.lock
@ -3602,6 +3602,25 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@lowdefy/connection-google-sheets@workspace:packages/connections/google-sheets":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@lowdefy/connection-google-sheets@workspace:packages/connections/google-sheets"
|
||||
dependencies:
|
||||
"@babel/cli": 7.15.7
|
||||
"@babel/core": 7.15.8
|
||||
"@babel/preset-env": 7.15.8
|
||||
"@lowdefy/ajv": 3.22.0
|
||||
"@lowdefy/helpers": 3.22.0
|
||||
babel-jest: 27.3.1
|
||||
google-spreadsheet: 3.1.15
|
||||
jest: 26.6.3
|
||||
mingo: 4.2.0
|
||||
moment: 2.29.1
|
||||
peerDependencies:
|
||||
"@lowdefy/api": 3.22.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@lowdefy/connection-knex@workspace:packages/connections/knex":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@lowdefy/connection-knex@workspace:packages/connections/knex"
|
||||
|
Loading…
x
Reference in New Issue
Block a user