@friggframework/devtools 1.2.0-canary.293.5731c31.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +117 -0
- package/README.md +80 -0
- package/frigg-cli/backendJs.js +33 -0
- package/frigg-cli/backendPath.js +26 -0
- package/frigg-cli/commitChanges.js +16 -0
- package/frigg-cli/environmentVariables.js +134 -0
- package/frigg-cli/environmentVariables.test.js +86 -0
- package/frigg-cli/index.js +14 -0
- package/frigg-cli/index.test.js +109 -0
- package/frigg-cli/installCommand.js +57 -0
- package/frigg-cli/installPackage.js +13 -0
- package/frigg-cli/integrationFile.js +30 -0
- package/frigg-cli/logger.js +12 -0
- package/frigg-cli/template.js +90 -0
- package/frigg-cli/validatePackage.js +79 -0
- package/index.js +2 -6
- package/package.json +16 -6
- package/test/auther-definition-method-tester.js +45 -0
- package/test/auther-definition-tester.js +125 -0
- package/test/index.js +11 -0
- package/{test-environment → test}/mock-api.js +1 -1
- package/test/mock-integration.js +83 -0
- package/eslint-config/CHANGELOG.md +0 -17
- package/eslint-config/LICENSE.md +0 -9
- package/eslint-config/README.md +0 -3
- package/eslint-config/bump3.txt +0 -0
- package/eslint-config/index.js +0 -38
- package/migrations/README.md +0 -3
- package/migrations/bump3.txt +0 -0
- package/migrations/jest.config.js +0 -3
- package/prettier-config/.eslintrc.json +0 -3
- package/prettier-config/CHANGELOG.md +0 -17
- package/prettier-config/LICENSE.md +0 -9
- package/prettier-config/README.md +0 -3
- package/prettier-config/bump3.txt +0 -0
- package/prettier-config/index.js +0 -6
- package/test-environment/.eslintrc.json +0 -3
- package/test-environment/Authenticator.js +0 -74
- package/test-environment/CHANGELOG.md +0 -46
- package/test-environment/LICENSE.md +0 -9
- package/test-environment/README.md +0 -3
- package/test-environment/bump3.txt +0 -0
- package/test-environment/index.js +0 -21
- package/test-environment/jest-global-setup.js +0 -6
- package/test-environment/jest-global-teardown.js +0 -3
- package/test-environment/jest-preset.js +0 -14
- package/test-environment/mongodb.js +0 -22
- package/test-environment/override-environment.js +0 -11
- /package/{eslint-config/.eslintrc.json → .eslintrc.json} +0 -0
- /package/{test-environment → test}/integration-validator.js +0 -0
- /package/{test-environment → test}/mock-api-readme.md +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const { installPackage } = require('./installPackage');
|
|
2
|
+
const { createIntegrationFile } = require('./integrationFile');
|
|
3
|
+
const { resolve } = require('node:path');
|
|
4
|
+
const { updateBackendJsFile } = require('./backendJs');
|
|
5
|
+
const { logInfo, logError } = require('./logger');
|
|
6
|
+
const { commitChanges } = require('./commitChanges');
|
|
7
|
+
const {
|
|
8
|
+
findNearestBackendPackageJson,
|
|
9
|
+
validateBackendPath,
|
|
10
|
+
} = require('./backendPath');
|
|
11
|
+
const { handleEnvVariables } = require('./environmentVariables');
|
|
12
|
+
const {
|
|
13
|
+
validatePackageExists,
|
|
14
|
+
searchAndSelectPackage,
|
|
15
|
+
} = require('./validatePackage');
|
|
16
|
+
|
|
17
|
+
const installCommand = async (apiModuleName) => {
|
|
18
|
+
try {
|
|
19
|
+
const packageNames = await searchAndSelectPackage(apiModuleName);
|
|
20
|
+
if (!packageNames || packageNames.length === 0) return;
|
|
21
|
+
|
|
22
|
+
const backendPath = findNearestBackendPackageJson();
|
|
23
|
+
validateBackendPath(backendPath);
|
|
24
|
+
|
|
25
|
+
for (const packageName of packageNames) {
|
|
26
|
+
await validatePackageExists(packageName);
|
|
27
|
+
installPackage(backendPath, packageName);
|
|
28
|
+
|
|
29
|
+
const modulePath = resolve(
|
|
30
|
+
backendPath,
|
|
31
|
+
`../../node_modules/${packageName}`
|
|
32
|
+
);
|
|
33
|
+
const {
|
|
34
|
+
Config: { label },
|
|
35
|
+
Api: ApiClass,
|
|
36
|
+
} = require(modulePath);
|
|
37
|
+
|
|
38
|
+
const sanitizedLabel = label.replace(
|
|
39
|
+
/[<>:"/\\|?*\x00-\x1F\s]/g,
|
|
40
|
+
''
|
|
41
|
+
); // Remove invalid characters and spaces console.log('Installing integration for:', sanitizedLabel);
|
|
42
|
+
createIntegrationFile(backendPath, sanitizedLabel, ApiClass);
|
|
43
|
+
updateBackendJsFile(backendPath, sanitizedLabel);
|
|
44
|
+
commitChanges(backendPath, sanitizedLabel);
|
|
45
|
+
logInfo(
|
|
46
|
+
`Successfully installed ${packageName} and updated the project.`
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
await handleEnvVariables(backendPath, modulePath);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logError('An error occurred:', error);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
module.exports = { installCommand };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function installPackage(backendPath, packageName) {
|
|
5
|
+
execSync(`npm install ${packageName}`, {
|
|
6
|
+
cwd: path.dirname(backendPath),
|
|
7
|
+
stdio: 'inherit',
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
installPackage,
|
|
13
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { logInfo } = require('./logger');
|
|
4
|
+
const { getIntegrationTemplate } = require('./template');
|
|
5
|
+
const INTEGRATIONS_DIR = 'src/integrations';
|
|
6
|
+
|
|
7
|
+
function createIntegrationFile(backendPath, apiModuleName, ApiClass) {
|
|
8
|
+
const integrationDir = path.join(
|
|
9
|
+
path.dirname(backendPath),
|
|
10
|
+
INTEGRATIONS_DIR
|
|
11
|
+
);
|
|
12
|
+
logInfo(`Ensuring directory exists: ${integrationDir}`);
|
|
13
|
+
fs.ensureDirSync(integrationDir);
|
|
14
|
+
|
|
15
|
+
const integrationFilePath = path.join(
|
|
16
|
+
integrationDir,
|
|
17
|
+
`${apiModuleName}Integration.js`
|
|
18
|
+
);
|
|
19
|
+
logInfo(`Writing integration file: ${integrationFilePath}`);
|
|
20
|
+
const integrationTemplate = getIntegrationTemplate(
|
|
21
|
+
apiModuleName,
|
|
22
|
+
backendPath,
|
|
23
|
+
ApiClass
|
|
24
|
+
);
|
|
25
|
+
fs.writeFileSync(integrationFilePath, integrationTemplate);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
createIntegrationFile,
|
|
30
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
function getIntegrationTemplate(apiModuleName, backendPath, ApiClass) {
|
|
4
|
+
// Find the sample data method
|
|
5
|
+
const apiMethods = Object.getOwnPropertyNames(ApiClass.prototype);
|
|
6
|
+
const sampleDataMethod =
|
|
7
|
+
apiMethods.find(
|
|
8
|
+
(method) =>
|
|
9
|
+
method.toLowerCase().includes('search') ||
|
|
10
|
+
method.toLowerCase().includes('list') ||
|
|
11
|
+
method.toLowerCase().includes('get')
|
|
12
|
+
) || 'searchDeals';
|
|
13
|
+
|
|
14
|
+
return `const { get, IntegrationBase, Options } = require('@friggframework/core');
|
|
15
|
+
const { Definition: ${apiModuleName}Module, Config: defaultConfig } = require('@friggframework/api-module-${apiModuleName.toLowerCase()}');
|
|
16
|
+
|
|
17
|
+
class ${apiModuleName}Integration extends IntegrationBase {
|
|
18
|
+
static Config = {
|
|
19
|
+
name: defaultConfig.name || '${apiModuleName.toLowerCase()}',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
supportedVersions: ['1.0.0'],
|
|
22
|
+
events: ['SEARCH_DEALS'],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
static Options =
|
|
26
|
+
new Options({
|
|
27
|
+
module: ${apiModuleName}Module,
|
|
28
|
+
integrations: [${apiModuleName}Module],
|
|
29
|
+
display: {
|
|
30
|
+
name: defaultConfig.displayName || '${apiModuleName}',
|
|
31
|
+
description: defaultConfig.description || 'Sales & CRM, Marketing',
|
|
32
|
+
category: defaultConfig.category || 'Sales & CRM, Marketing',
|
|
33
|
+
detailsUrl: defaultConfig.detailsUrl || 'https://www.${apiModuleName.toLowerCase()}.com',
|
|
34
|
+
icon: defaultConfig.icon || 'https://friggframework.org/assets/img/${apiModuleName.toLowerCase()}.jpeg',
|
|
35
|
+
},
|
|
36
|
+
hasUserConfig: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
static modules = {
|
|
40
|
+
${apiModuleName.toLowerCase()}: ${apiModuleName}Module,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* HANDLE EVENTS
|
|
45
|
+
*/
|
|
46
|
+
async receiveNotification(notifier, event, object = null) {
|
|
47
|
+
if (event === 'SEARCH_DEALS') {
|
|
48
|
+
return this.target.api.searchDeals(object);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* ALL CUSTOM/OPTIONAL METHODS FOR AN INTEGRATION MANAGER
|
|
54
|
+
*/
|
|
55
|
+
async getSampleData() {
|
|
56
|
+
const res = await this.target.api.${sampleDataMethod}();
|
|
57
|
+
return { data: res };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* ALL REQUIRED METHODS FOR AN INTEGRATION MANAGER
|
|
62
|
+
*/
|
|
63
|
+
async onCreate(params) {
|
|
64
|
+
// Validate that we have all of the data we need
|
|
65
|
+
// Set integration status as makes sense. Default ENABLED
|
|
66
|
+
// TODO turn this into a validateConfig method/function
|
|
67
|
+
this.record.status = 'ENABLED';
|
|
68
|
+
await this.record.save();
|
|
69
|
+
return this.record;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async onUpdate(params) {
|
|
73
|
+
const newConfig = get(params, 'config');
|
|
74
|
+
const oldConfig = this.record.config;
|
|
75
|
+
// Just save whatever
|
|
76
|
+
this.record.markModified('config');
|
|
77
|
+
await this.record.save();
|
|
78
|
+
return this.validateConfig();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getConfigOptions() {
|
|
82
|
+
const options = {}
|
|
83
|
+
return options;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = ${apiModuleName}Integration;`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { getIntegrationTemplate };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
const { logError } = require('./logger');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
|
|
6
|
+
async function searchPackages(apiModuleName) {
|
|
7
|
+
const searchCommand = `npm search @friggframework/api-module-${apiModuleName} --json`;
|
|
8
|
+
const result = execSync(searchCommand, { encoding: 'utf8' });
|
|
9
|
+
return JSON.parse(result);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function checkPackageExists(packageName) {
|
|
13
|
+
try {
|
|
14
|
+
const response = await axios.get(
|
|
15
|
+
`https://registry.npmjs.org/${packageName}`
|
|
16
|
+
);
|
|
17
|
+
return response.status === 200;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function validatePackageExists(packageName) {
|
|
24
|
+
const packageExists = await checkPackageExists(packageName);
|
|
25
|
+
if (!packageExists) {
|
|
26
|
+
throw new Error(`Package ${packageName} does not exist on npm.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const searchAndSelectPackage = async (apiModuleName) => {
|
|
31
|
+
const searchResults = await searchPackages(apiModuleName || '');
|
|
32
|
+
|
|
33
|
+
if (searchResults.length === 0) {
|
|
34
|
+
logError(`No packages found matching ${apiModuleName}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const filteredResults = searchResults.filter((pkg) => {
|
|
39
|
+
const version = pkg.version
|
|
40
|
+
? pkg.version.split('.').map(Number)
|
|
41
|
+
: [];
|
|
42
|
+
return version[0] >= 1;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (filteredResults.length === 0) {
|
|
46
|
+
const earlierVersions = searchResults
|
|
47
|
+
.map((pkg) => `${pkg.name} (${pkg.version})`)
|
|
48
|
+
.join(', ');
|
|
49
|
+
logError(
|
|
50
|
+
`No packages found with version 1.0.0 or above for ${apiModuleName}. Found earlier versions: ${earlierVersions}`
|
|
51
|
+
);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const choices = filteredResults.map((pkg) => {
|
|
56
|
+
return {
|
|
57
|
+
name: `${pkg.name} (${pkg.version})`,
|
|
58
|
+
checked: filteredResults.length === 1, // Automatically select if only one result
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const { selectedPackages } = await inquirer.prompt([
|
|
63
|
+
{
|
|
64
|
+
type: 'checkbox',
|
|
65
|
+
name: 'selectedPackages',
|
|
66
|
+
message: 'Select the packages to install:',
|
|
67
|
+
choices,
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
return selectedPackages.map(choice => choice.split(' ')[0]);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
validatePackageExists,
|
|
76
|
+
checkPackageExists,
|
|
77
|
+
searchPackages,
|
|
78
|
+
searchAndSelectPackage,
|
|
79
|
+
};
|
package/index.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
const
|
|
2
|
-
const prettierConfig = require('./prettier-config')
|
|
3
|
-
const testEnvironment = require('./test-environment/index');
|
|
1
|
+
const test = require('./test');
|
|
4
2
|
|
|
5
3
|
module.exports = {
|
|
6
|
-
|
|
7
|
-
prettierConfig,
|
|
8
|
-
...testEnvironment
|
|
4
|
+
...test
|
|
9
5
|
}
|
package/package.json
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@babel/eslint-parser": "^7.18.9",
|
|
7
|
+
"@babel/parser": "^7.25.3",
|
|
8
|
+
"@babel/traverse": "^7.25.3",
|
|
9
|
+
"@friggframework/core": "^1.2.1",
|
|
10
|
+
"@friggframework/test": "^1.2.1",
|
|
11
|
+
"axios": "^1.7.2",
|
|
12
|
+
"commander": "^12.1.0",
|
|
13
|
+
"dotenv": "^16.4.5",
|
|
7
14
|
"eslint": "^8.22.0",
|
|
8
15
|
"eslint-config-prettier": "^8.5.0",
|
|
9
16
|
"eslint-plugin-json": "^3.1.0",
|
|
10
17
|
"eslint-plugin-markdown": "^3.0.0",
|
|
11
18
|
"eslint-plugin-no-only-tests": "^3.0.0",
|
|
12
19
|
"eslint-plugin-yaml": "^0.5.0",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
20
|
+
"fs-extra": "^11.2.0",
|
|
21
|
+
"inquirer": "^10.1.6"
|
|
15
22
|
},
|
|
16
23
|
"devDependencies": {
|
|
17
|
-
"
|
|
18
|
-
"prettier": "^2.
|
|
24
|
+
"@friggframework/eslint-config": "^1.2.1",
|
|
25
|
+
"@friggframework/prettier-config": "^1.2.1"
|
|
19
26
|
},
|
|
20
27
|
"scripts": {
|
|
21
28
|
"lint:fix": "prettier --write --loglevel error . && eslint . --fix",
|
|
22
29
|
"test": "jest --passWithNoTests # TODO"
|
|
23
30
|
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"frigg": "./frigg-cli/index.js"
|
|
33
|
+
},
|
|
24
34
|
"author": "",
|
|
25
35
|
"license": "MIT",
|
|
26
36
|
"main": "index.js",
|
|
@@ -36,5 +46,5 @@
|
|
|
36
46
|
"publishConfig": {
|
|
37
47
|
"access": "public"
|
|
38
48
|
},
|
|
39
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "3c2bb949c71be314cf44e326e618b3e16b787e8c"
|
|
40
50
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const {flushDebugLog} = require('@friggframework/core');
|
|
2
|
+
|
|
3
|
+
async function testDefinitionRequiredAuthMethods(api, definition, authCallbackParams, tokenResponse, userId) {
|
|
4
|
+
|
|
5
|
+
// const response = await definition.getToken(api, authCallbackParams);
|
|
6
|
+
// expect(api.setTokens).toHaveBeenCalled();
|
|
7
|
+
// if (tokenResponse) {
|
|
8
|
+
// expect(response).toMatchObject(tokenResponse);
|
|
9
|
+
// }
|
|
10
|
+
|
|
11
|
+
const entityDetails = await definition.requiredAuthMethods.getEntityDetails(api, authCallbackParams, tokenResponse, userId);
|
|
12
|
+
expect(entityDetails).toHaveProperty('identifiers');
|
|
13
|
+
expect(Object.values(entityDetails.identifiers).length).toBeGreaterThan(0);
|
|
14
|
+
for (const key of Object.keys(entityDetails.identifiers)){
|
|
15
|
+
expect(key).toBeDefined();
|
|
16
|
+
expect(entityDetails.identifiers[key]).toBeDefined();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const credentialDetails = await definition.requiredAuthMethods.getCredentialDetails(api);
|
|
21
|
+
expect(credentialDetails).toHaveProperty('identifiers');
|
|
22
|
+
expect(Object.values(entityDetails.identifiers).length).toBeGreaterThan(0);
|
|
23
|
+
for (const key of Object.keys(entityDetails.identifiers)){
|
|
24
|
+
expect(key).toBeDefined();
|
|
25
|
+
expect(entityDetails.identifiers[key]).toBeDefined();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const successResponse = await definition.requiredAuthMethods.testAuthRequest(api);
|
|
29
|
+
expect(successResponse).toBeTruthy();
|
|
30
|
+
const savedKeys = {};
|
|
31
|
+
for (const key of definition.requiredAuthMethods.apiPropertiesToPersist.credential){
|
|
32
|
+
savedKeys[key] = api[key];
|
|
33
|
+
delete api[key];
|
|
34
|
+
}
|
|
35
|
+
let validAuth = false;
|
|
36
|
+
try {
|
|
37
|
+
if (await definition.requiredAuthMethods.testAuthRequest(api)) validAuth = true;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
flushDebugLog(e);
|
|
40
|
+
}
|
|
41
|
+
expect(validAuth).not.toBeTruthy();
|
|
42
|
+
Object.assign(api, savedKeys);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { testDefinitionRequiredAuthMethods }
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const {
|
|
2
|
+
Auther,
|
|
3
|
+
ModuleConstants,
|
|
4
|
+
createObjectId,
|
|
5
|
+
connectToDatabase,
|
|
6
|
+
disconnectFromDatabase,
|
|
7
|
+
} = require('@friggframework/core');
|
|
8
|
+
const { createMockApiObject } = require("./mock-integration");
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function testAutherDefinition(definition, mocks) {
|
|
12
|
+
const getModule = async (params) => {
|
|
13
|
+
const module = await Auther.getInstance({
|
|
14
|
+
definition,
|
|
15
|
+
userId: createObjectId(),
|
|
16
|
+
...params,
|
|
17
|
+
});
|
|
18
|
+
if (mocks.tokenResponse) {
|
|
19
|
+
mocks.getTokenFrom = async function(code) {
|
|
20
|
+
await this.setTokens(mocks.tokenResponse);
|
|
21
|
+
return mocks.tokenResponse
|
|
22
|
+
}
|
|
23
|
+
mocks.getTokenFromCode = mocks.getTokenFromCode || mocks.getTokenFrom
|
|
24
|
+
mocks.getTokenFromCodeBasicAuthHeader = mocks.getTokenFromCodeBasicAuthHeader || mocks.getTokenFrom
|
|
25
|
+
mocks.getTokenFromClientCredentials = mocks.getTokenFromClientCredentials || mocks.getTokenFrom
|
|
26
|
+
mocks.getTokenFromUsernamePassword = mocks.getTokenFromUsernamePassword || mocks.getTokenFrom
|
|
27
|
+
}
|
|
28
|
+
if (mocks.refreshResponse) {
|
|
29
|
+
mocks.refreshAccessToken = async function(code) {
|
|
30
|
+
await this.setTokens(mocks.refreshResponse);
|
|
31
|
+
return mocks.refreshResponse
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
module.api = createMockApiObject(jest, module.api, mocks);
|
|
35
|
+
return module
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
describe(`${definition.moduleName} Module Tests`, () => {
|
|
40
|
+
let module, authUrl;
|
|
41
|
+
beforeAll(async () => {
|
|
42
|
+
await connectToDatabase();
|
|
43
|
+
module = await getModule();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(async () => {
|
|
47
|
+
await disconnectFromDatabase();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let requirements, authCallbackParams;
|
|
51
|
+
if (definition.API.requesterType === ModuleConstants.authType.oauth2) {
|
|
52
|
+
authCallbackParams = mocks.authorizeResponse || mocks.authorizeParams;
|
|
53
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
54
|
+
it('should return auth requirements', async () => {
|
|
55
|
+
requirements = await module.getAuthorizationRequirements();
|
|
56
|
+
expect(requirements).toBeDefined();
|
|
57
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.oauth2);
|
|
58
|
+
expect(requirements.url).toBeDefined();
|
|
59
|
+
authUrl = requirements.url;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
} else if (definition.API.requesterType === ModuleConstants.authType.basic) {
|
|
63
|
+
// could also confirm authCallbackParams against the auth requirements
|
|
64
|
+
authCallbackParams = mocks.authorizeParams
|
|
65
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
66
|
+
it('should return auth requirements', async () => {
|
|
67
|
+
requirements = module.getAuthorizationRequirements();
|
|
68
|
+
expect(requirements).toBeDefined();
|
|
69
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.basic);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
} else if (definition.API.requesterType === ModuleConstants.authType.apiKey) {
|
|
73
|
+
// could also confirm authCallbackParams against the auth requirements
|
|
74
|
+
authCallbackParams = mocks.authorizeParams
|
|
75
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
76
|
+
it('should return auth requirements', async () => {
|
|
77
|
+
requirements = module.getAuthorizationRequirements();
|
|
78
|
+
expect(requirements).toBeDefined();
|
|
79
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.apiKey);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe('Authorization requests', () => {
|
|
85
|
+
let firstRes;
|
|
86
|
+
it('processAuthorizationCallback()', async () => {
|
|
87
|
+
firstRes = await module.processAuthorizationCallback(authCallbackParams);
|
|
88
|
+
expect(firstRes).toBeDefined();
|
|
89
|
+
expect(firstRes.entity_id).toBeDefined();
|
|
90
|
+
expect(firstRes.credential_id).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('retrieves existing entity on subsequent calls', async () => {
|
|
93
|
+
const res = await module.processAuthorizationCallback(authCallbackParams);
|
|
94
|
+
expect(res).toEqual(firstRes);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('Test credential retrieval and module instantiation', () => {
|
|
99
|
+
it('retrieve by entity id', async () => {
|
|
100
|
+
const newModule = await getModule({
|
|
101
|
+
userId: module.userId,
|
|
102
|
+
entityId: module.entity.id
|
|
103
|
+
});
|
|
104
|
+
expect(newModule).toBeDefined();
|
|
105
|
+
expect(newModule.entity).toBeDefined();
|
|
106
|
+
expect(newModule.credential).toBeDefined();
|
|
107
|
+
expect(await newModule.testAuth()).toBeTruthy();
|
|
108
|
+
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('retrieve by credential id', async () => {
|
|
112
|
+
const newModule = await getModule({
|
|
113
|
+
userId: module.userId,
|
|
114
|
+
credentialId: module.credential.id
|
|
115
|
+
});
|
|
116
|
+
expect(newModule).toBeDefined();
|
|
117
|
+
expect(newModule.credential).toBeDefined();
|
|
118
|
+
expect(await newModule.testAuth()).toBeTruthy();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { testAutherDefinition }
|
|
125
|
+
|
package/test/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const {testDefinitionRequiredAuthMethods} = require('./auther-definition-method-tester');
|
|
2
|
+
const {createMockIntegration, createMockApiObject} = require('./mock-integration');
|
|
3
|
+
const { testAutherDefinition } = require('./auther-definition-tester');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
createMockIntegration,
|
|
8
|
+
createMockApiObject,
|
|
9
|
+
testDefinitionRequiredAuthMethods,
|
|
10
|
+
testAutherDefinition,
|
|
11
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const nock = require('nock');
|
|
2
|
-
const Authenticator = require('
|
|
2
|
+
const { Authenticator } = require('@friggframework/test');
|
|
3
3
|
const { join: joinPath } = require('path');
|
|
4
4
|
const { parse: parseUrl } = require('url');
|
|
5
5
|
const { mkdir, readFile, rename, rm, writeFile } = require('fs/promises');
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const { Auther, Credential, Entity, IntegrationFactory, createObjectId } = require('@friggframework/core');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
async function createMockIntegration(IntegrationClassDef, userId = null, config = {},) {
|
|
5
|
+
const integrationFactory = new IntegrationFactory([IntegrationClassDef]);
|
|
6
|
+
userId = userId || createObjectId();
|
|
7
|
+
|
|
8
|
+
const insertOptions = {
|
|
9
|
+
new: true,
|
|
10
|
+
upsert: true,
|
|
11
|
+
setDefaultsOnInsert: true,
|
|
12
|
+
}
|
|
13
|
+
const user = {user: userId}
|
|
14
|
+
|
|
15
|
+
const credential = await Credential.findOneAndUpdate(
|
|
16
|
+
user,
|
|
17
|
+
{ $set: user },
|
|
18
|
+
insertOptions
|
|
19
|
+
);
|
|
20
|
+
const entity1 = await Entity.findOneAndUpdate(
|
|
21
|
+
user,
|
|
22
|
+
{
|
|
23
|
+
$set: {
|
|
24
|
+
credential: credential.id,
|
|
25
|
+
user: userId,
|
|
26
|
+
name: 'Test user',
|
|
27
|
+
externalId: '1234567890123456',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
insertOptions
|
|
31
|
+
);
|
|
32
|
+
const entity2 = await Entity.findOneAndUpdate(
|
|
33
|
+
user,
|
|
34
|
+
{
|
|
35
|
+
$set: {
|
|
36
|
+
credential: credential.id,
|
|
37
|
+
user: userId,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
insertOptions
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const entities = [entity1, entity2]
|
|
44
|
+
|
|
45
|
+
const integration =
|
|
46
|
+
await integrationFactory.createIntegration(
|
|
47
|
+
entities,
|
|
48
|
+
userId,
|
|
49
|
+
config,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
integration.id = integration.record._id
|
|
53
|
+
|
|
54
|
+
for (const i in entities){
|
|
55
|
+
if (Object.entries(IntegrationClassDef.modules).length <= i) break
|
|
56
|
+
const [moduleName, ModuleDef] = Object.entries(IntegrationClassDef.modules)[i];
|
|
57
|
+
const module = await Auther.getInstance({definition: ModuleDef, userId: userId})
|
|
58
|
+
module.entity = entities[i];
|
|
59
|
+
integration[moduleName] = module;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return integration
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createMockApiObject(jest, api = {}, mockMethodMap) {
|
|
66
|
+
// take in an api class and object with keys that are method names
|
|
67
|
+
// and values which are the mock response (or implementation)
|
|
68
|
+
const clone = (data) => JSON.parse(JSON.stringify(data));
|
|
69
|
+
|
|
70
|
+
for (const [methodName, mockDataOrImplementation] of Object.entries(mockMethodMap)) {
|
|
71
|
+
if (mockDataOrImplementation instanceof Function) {
|
|
72
|
+
api[methodName] = jest.fn(mockDataOrImplementation);
|
|
73
|
+
}
|
|
74
|
+
else if (api[methodName]?.constructor?.name === "AsyncFunction") {
|
|
75
|
+
api[methodName] = jest.fn().mockResolvedValue(clone(mockDataOrImplementation));
|
|
76
|
+
} else {
|
|
77
|
+
api[methodName] = jest.fn().mockReturnValue(clone(mockDataOrImplementation));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return api;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {createMockIntegration, createMockApiObject};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# v1.0.8 (Mon Jan 09 2023)
|
|
2
|
-
|
|
3
|
-
#### 🐛 Bug Fix
|
|
4
|
-
|
|
5
|
-
- Merge remote-tracking branch 'origin/main' into gitbook-updates [#48](https://github.com/friggframework/frigg/pull/48) ([@seanspeaks](https://github.com/seanspeaks))
|
|
6
|
-
- Bump independent versions \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
|
|
7
|
-
- Merge remote-tracking branch 'origin/main' into simplify-mongoose-models ([@seanspeaks](https://github.com/seanspeaks))
|
|
8
|
-
- Add READMEs for all packages and api-modules [#20](https://github.com/friggframework/frigg/pull/20) ([@seanspeaks](https://github.com/seanspeaks))
|
|
9
|
-
- Add READMEs for all packages and api-modules ([@seanspeaks](https://github.com/seanspeaks))
|
|
10
|
-
|
|
11
|
-
#### ⚠️ Pushed to `main`
|
|
12
|
-
|
|
13
|
-
- Refactored for more conventional naming (at least for packages) ([@seanspeaks](https://github.com/seanspeaks))
|
|
14
|
-
|
|
15
|
-
#### Authors: 1
|
|
16
|
-
|
|
17
|
-
- Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
|
package/eslint-config/LICENSE.md
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Left Hook Inc.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
-
|
|
7
|
-
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
|
8
|
-
|
|
9
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/eslint-config/README.md
DELETED
package/eslint-config/bump3.txt
DELETED
|
File without changes
|