ITENTIAL ADAPTERS TECHNICAL RESOURCE
Adapter Testing
Adapter Test Files
These are the test files used for adapter testing.
test/unit/adapterTestUnit.js:
Contains the adapter unit tests. Unit tests are for the foundational components of the adapter – existing code and configurations contained within the adapter’s files. The unit tests also include testing for most error conditions (e.g., required fields) within the adapter.js.
test/integration/adapterTestIntegration.js:
Contains the adapter integration tests. Integration tests are for testing integration with the other system. These tests can be run in standalone ”stub” mode or be integrated with the other system. The only error conditions tested in the integration tests are no mock data or errors returned from the other system and how they are handled.
utils/testRunner.js:
This is a utility that can be used for easier testing. It allows you to provide credentials for integration testing without saving them in the test scripts. Of note, this test file is flawed and not as good for debugging issues.
package.json:
Contains several testing scripts: test:unit, test:integration, test:cover, and test. You can change the log level on the test:unit and test:integration scripts in the package.json to get better logs from the adapter during testing.
Adapter Linting
- Where is the linting script?
- The package.json contains the linting scripts. You can lint the adapter by simply running npm run lint in the adapter directory.
- Linting can show a large number of warnings in the code. You can also run lint to just see the errors by running npm run lint:errors
- What is the purpose of linting?
- Linting provides the ability to find and fix potential runtime errors. It also improves code quality.
- What linting tool is used?
- ESLint
- What are the linting rules?
- The adapters are linted using AirBnB standards with some slight modifications:
- Change the max length to a warning.
- Remove the comma-dangle so that JSON is formatted as JSON.
- The adapters are linted using AirBnB standards with some slight modifications:
Basic Linting Rules
- Indentation should be 2 spaces.
- No double quotes. Use single quotes instead. Ex: ‘I am a string’
- When using variables in a string use slant quotes. Ex: `this adds a ${variable} into a string`
- Do not use quotes around keys in objects. Ex: { key: ‘value’ }
- Use dot notation for object references unless there is a – in the key. Ex: object.key
- If there is a – in the key, use [] to reference it. Ex: object[my-key]
- Never use ++ or – use var += or – = instead. Ex: x+= 1
- Space after commas.
- Space before curly braces on functions and objects.
- Else should be on the same line as the end of if.
- Catch should be on the same line as the end of try.
- No spaces at the end of code lines.
What’s in the Unit Test?
Generic Tests
These unit tests are consistent across all adapters. They should not be modified and will be updated when the adapter is migrated (the process of updating the adapter foundation).
- Class: Tests that the adapter has been instantiated with the defined properties.
- Adapter Base: Tests for the existence of the adapter base.
- Workflow Functions: Tests for existing workflow functions. The results of these tests will be used in a later test.
- Package: Tests that the package.json file exists, it is a valid JSON, and that it has been customized for the adapter.
- Pronghorn: Tests that the pronghorn.json file exists, that it has been customized for the adapter, that the adapter.js methods and pronghorn.json methods are in sync, and all parameters match to reduce the risk of issues in the Itential Automation Platform (IAP) workflow tasks.
- Properties Schema: Tests that the propertiesSchema.json exists and that it has been customized for the adapter.
- Error: Tests that the error.json exists.
- Sample Properties: Tests that the sampleProperties.json exists.
- Readme: Tests that the README.md file exists and that it has been customized for the adapter.
- Connect: Tests that the connect method exists.
- Healthcheck: Tests that the healthCheck method exists.
- Check Action Files: Tests that the checkActionFiles method exists and it also checks all the action files against the actionSchema (done within the adapter library). Also checks that the referenced files exist. This prevents possible issues with actions not working later.
- Encrypt Property: Tests that the encryptProperty method exists. It will also test that encoding (b64) and encrypting (AES) of a property works as expected.
ADAPTERTESTUNIT.JS
describe('#class instance created', () => {
it('should be a class with properties', (done) => {
describe('adapterBase.js', () => {
it('should have an adapterBase.js', (done) => {
describe('#getWorkflowFunctions', () => {
it('should retrieve workflow functions', (done) => {
describe('package.json', () => {
it('should have a package.json', (done) => {
it('package.json should be validated', (done) => {
it('package.json should be customized', (done) => {
describe('pronghorn.json', () => {
it('should have a pronghorn.json', (done) => {
it('pronghorn.json should be customized', (done) => {
it('pronghorn.json should only expose workflow functions', (done) => {
it('pronghorn.json should expose all workflow functions', (done) => {
describe('propertiesSchema.json', () => {
it('should have a propertiesSchema.json', (done) => {
it('propertiesSchema.json should be customized', (done) => {
describe('error.json', () => {
it('should have an error.json', (done) => {
describe('sampleProperties.json', () => {
it('should have a sampleProperties.json', (done) => {
it('should have a checkProperties function', (done) => {
it('the sample properties should be good - if failure change the log level', (done) => {
describe('README.md', () => {
it('should have a README', (done) => {
it('README.md should be customized', (done) => {
describe('#connect', () => {
it('should have a connect function', (done) => {
describe('#healthCheck', () => {
it('should have a healthCheck function', (done) => {
describe('#checkActionFiles', () => {
it('should have a checkActionFiles function', (done) => {
it('the action files should be good - if failure change the log level as most issues are warnings', (done) => {
describe('#encryptProperty', () => {
it('should have a encryptProperty function', (done) => {
it('should get base64 encoded property', (done) => {
it('should get encrypted property', (done) => {
Has Entity
This test is commented out for future use.
Note: There is a marker in the file to separate the generic part of the file from the customizable part.
ADAPTERTESTUNIT.JS
/*
-----------------------------------------------------------------------
-----------------------------------------------------------------------
*** All code above this comment will be replaced during a migration ***
******************* DO NOT REMOVE THIS COMMENT BLOCK ******************
-----------------------------------------------------------------------
-----------------------------------------------------------------------
*/
Specific Tests
The Adapter Builder will create tests for every API method created in the adapter.js file. These tests are specific to the adapter and can be customized as needed to enhance/ extend testing.
First Test
This will test that the adapter API method exists.
ADAPTERTESTUNIT.JS
describe('#createTenant', () => {
it('should have a createTenant function', (done) => {
try {
assert.equal(true, typeof a.createTenant === 'function’);
done();
} catch (error) {
log.error(`Test Failure: ${error}`);
done(error);
}
}).timeout(attemptTimeout);
});
Second Test
This will test an error when a required parameter has not been provided.
runErrorAsserts: This is a private function that tests that the appropriate error object has been received.
ADAPTERTESTUNIT.JS
describe('#createTenant', () => {
it('should error if - missing tenant name', (done) => {
try {
a.createTenant(null, null, (data, error) => {
try {
const displayE = 'tenantName is required’;
runErrorAsserts(data, error, 'AD.300', 'test-apic-adapter-createTenant', displayE);
done();
} catch (err) {
log.error(`Test Failure: ${err}`);
done(err);
}
});
} catch (error) {
log.error(`Adapter Exception: ${error}`);
done(error);
}
}).timeout(attemptTimeout);
});
Third Test
This will test for Ajv failures when translating data. This can be a missing field or a field that has an invalid value (not the correct data type).
ADAPTERTESTUNIT.JS
describe('#createDevice', () => {
it('should error on create a device - no name', (done) => {
try {
const device = { ipAddress: '10.10.10.1' };
a.createDevice(device, (data, error) => {
try {
const displayE = 'Schema validation failed on should have required property \'.name\’’;
runErrorAsserts(data, error, 'AD.312', 'Test-sevone-translatorUtil-buildJSONEntity', displayE);
done();
} catch (err) {
log.error(`Test Failure: ${err}`);
done(err);
}
});
} catch (error) {
log.error(`Adapter Exception: ${error}`);
done(error);
}
}).timeout(attemptTimeout);
});
What’s in the Integration Test?
Generic Tests
These integration tests are consistent across all adapters. They should not be modified and will be updated when the adapter is migrated (the process of updating the adapter foundation).
- Class: Tests that the adapter has been instantiated with the defined properties.
- Connect: Tests that the connect method is successful with no healthcheck, and with healthcheck on at startup.
- Healthcheck: Tests the healthcheck method is successful.
ADAPTERTESTINTEGRATION.JS
describe('#class instance created', () => {
it('should be a class with properties', (done) => {
describe('#connect', () => {
it('should get connected - no healthcheck', (done) => {
it('should get connected - startup healthcheck', (done) => {
describe('#healthCheck', () => {
it('should be healthy', (done) => {
Note: There is a marker in the file to separate the generic part of the file from the customizable part.
ADAPTERTESTINTEGRATION.JS
/*
-----------------------------------------------------------------------
-----------------------------------------------------------------------
*** All code above this comment will be replaced during a migration ***
******************* DO NOT REMOVE THIS COMMENT BLOCK ******************
-----------------------------------------------------------------------
-----------------------------------------------------------------------
*/
Specific Tests
The Adapter Builder will create an integration test for every API method created in the adapter.js file. These tests are specific to the adapter and can be customized as needed to enhance/ extend testing.
No Mock Data Provided
If there is no mock data, the stub test will test for an error.
The integration test provides the ability to save mock data when running integrated.
runCommonAsserts: This is a private function that tests some basic aspects of a valid response.
runErrorAsserts: This is a private function that tests some basic aspects of a valid error response.
ADAPTERTESTINTEGRATION.JS
describe('#createTenant', () => {
it('should work if integrated but since no mockdata should error when run standalone', (done) => {
try {
a.createTenant(tenantName, null, (data, error) => {
try {
if (stub) {
const displayE = 'Error 400 received on request’;
runErrorAsserts(data, error, 'AD.500', 'test-apic-connectorRest-handleEndResponse', displayE);
} else {
runCommonAsserts(data, error);
}
saveMockData('poc-apic', 'createTenant', 'default', data);
done();
} catch (err) {
log.error(`Test Failure: ${err}`);
done(err);
}
});
} catch (error) {
log.error(`Adapter Exception: ${error}`);
done(error);
}
}).timeout(attemptTimeout);
});
Mock Data Provided
When mock data has been provided, the stub test will test for the data that is in the mock data file.
ADAPTERTESTINTEGRATION.JS
describe('#createTenant', () => {
it('should work if integrated but since no mockdata should error when run standalone', (done) => {
try {
a.createTenant(tenantName, null, (data, error) => {
try {
if (stub) {
runCommonAsserts(data, error);
assert.equal('string', data.response.description);
assert.equal('string', data.response.ImportTaskId);
assert.equal('object', typeof data.response.SnapshotTaskDetail);
} else {
runCommonAsserts(data, error);
}
saveMockData('poc-apic', 'createTenant', 'default', data);
done();
} catch (err) {
log.error(`Test Failure: ${err}`);
done(err);
}
});
} catch (error) {
log.error(`Adapter Exception: ${error}`);
done(error);
}
}).timeout(attemptTimeout);
});
Testing Best Practices
When you download or build an adapter the following are some good test practices to follow.
1. Install the adapter into the directory where IAP will run it. This should be in IAPHome/node_modules/@<namespace>/adapter-<name>.
For example: /opt/pronghorn/current/node_modules/@itentialopensource/adapter-sevone.
2. Go into the adapter directory and install the adapter dependencies.
run npm install
3. Run linting on the adapter.
npm run lint
4. Run unit tests on the adapter.
npm run test:unit
5. Run standalone integration tests on the adapter.
npm run test:integration
6. If there is an instance of the other system that you can test, attempt to run integration tests with that system.
- Edit the properties in the integration test file accordingly.
- Run integration tests — npm run test:integration
- If there are any issues, turn debug logging on and attempt to determine what the issues are. Contact the Itential Adapters Team for help, if needed.
Adding Tests or Test Data
- Do not modify anything above the marker or it will be lost when migrating the adapter.
- You can modify existing method tests by:
- Adding additional asserts.
- Adding data to send on the requests.
- You can also add new tests.
- You can test additional errors using test double.
- You can use additional mock data to test errors as well.
- If you add calls to the adapter manually, you should add tests to the Unit and Integration test files so that those methods can be tested properly.
Saving Mock Data from Tests
- When you run integrated tests (stub = false) with the isSaveMockData flag turned on, the integration tests will save the response from the request into a mock data file and update the action.json file.
- If you decide to run standalone tests (stub = true), the response that is received will be used as a mock data response for a similar request.
- Each way makes gathering of mock data easier for delivery teams.
- If you are not using return_raw set to true and if your data is being translated, it is important to understand that what is being saved has already been translated. Therefore, you might need to edit the mock data and reverse the translation.
Intelligent Integration Testing
- Every method in the adapter has built-in integration tests. However, not a lot of intelligence was put into the process. It will use mock data and run standalone tests based on the mock data, but not much more.
- Integration tests often need to be executed in a specific order (i.e., create an item before retrieving or updating it, and lastly deleting it).
- Many integration tests depend upon data as required information that is needed to create an item.
- Often creating one item may require the existence of another item.
- New integration tests generated by the Adapter Builder take these factors into consideration based on the information that is put into the adapter. In other words, “if it can figure it out, it does”. This provides additional time savings into the process to build and test a useful adapter.
Testing Without Test Runner
Other than changing the log level as needed for debugging, these scripts should not be modified. They are utilized by pre-commit hooks and CI/CD pipelines.
- npm run test:unit
- Runs unit tests only.
- npm run test: integration
- Runs integration tests only.
- npm run test:cover
- Will run test coverage information. Note: This may not show test coverage of the adapter library.
- npm run test
- Will run both unit and integration tests.
Editing Scripts in package.json
You can edit the scripts in the package.json to change the log level for testing.
- The adapter logs the options for the HTTP request as well as the response it receives in debug mode. Hence, when errors occur, it is often a good idea to run the tests in debug mode.
- The adapter logs every step it takes in trace mode. This is often too chatty to be useful and should only be used to isolate what part of the adapter is having issues.
PACKAGE.JSON
"scripts": {
..
"test:unit": "mocha test/unit/adapterTestUnit.js --LOG=error",
"test:integration": "mocha test/integration/adapterTestIntegration.js --LOG=error",
"test:cover": "nyc --reporter html --reporter text mocha --reporter dot test/*",
"test": "npm run test:unit && npm run test:integration",
..
},
Running in Debug
When the adapter is run in debug, some of the important logs are logged out. These logs help you know exactly what is happening when communicating with the other system. This is true when the adapter is integrated with IAP but is also true when running standalone testing.
- OPTIONS: Provides all the call options. Authentication information is missing because we intentionally do not want to log any credentials. This is one of the most helpful logs when determining why things are not working as expected.
- REQUEST: Provides the payload that will be sent to the other system.
- CALL RETURN: The raw response that is returned.
- RESPONSE: The response being returned (only the first element if an array).
CONSOLE LOGS FROM TEST
debug: Test-eai-connectorRest-performRequest: OPTIONS: {"hostname":"replace.hostorip.here","port":80,"path":"/oss-core-ws/rest/oci/site/1/cascadeDelete?fs=dynAttrs%2CderivedAttrs","method":"POST","headers":{"GS-Database-Host-Name":"localhost","GS-Database-Name":"db_0834e8bc13d2b3d7a","interactive":false,"Content-Type":"application/json","Accept":"application/json"}}
debug: Test-eai-connectorRest-performRequest:REQUEST: {"name":"BERKELE1.updated","type":"oci/site"}
Test Runner
There is a utility in the adapters called testRunner that can be used to run any tests.
- node utils/testRunner.js
- With this utility, you can run unit tests, stand alone integration tests, and true integration tests. A true integration test will ask you for credentials. When this happens the test script itself will edit, run the integration tests, and then change the test script back.
- Benefits:
- You do not have to know how to edit an integration test script to run an integration test.
- Integration tests are not stored in a repository with credentials in them.
- Disadvantage:
- It is harder to debug failing tests due to how the console and logs appear when running the tests.
Adapter Support
If you experience any problems or can’t figure out how to run adapter testing , don’t hesitate to contact us. The Itential Adapters Team is here to help!
For help with testing:
- Create a ticket (ISD, IPSO or ADAPT).
- You can work the ticket or allow the Itential Adapter Team to verify if your tests have been set-up properly.
- Once all changes are made to your testing files, update the adapter-utils dependency in your adapter or run the adapterMigrator.
To update your adapter-utils dependency:
- Change the version in the package.json dependencies.
- rm –rf node modules
- rm package-lock.json
- npm install