Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated email-notifier to use Jest for testing. #75

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jgeewax
Copy link
Contributor

@jgeewax jgeewax commented Nov 13, 2019

I'm sorry in advance that this is such a big nasty scary pull request. I only changed actual code in cases where mocking wasn't possible to do safely (and in those cases, added extra tests for the new code).

I couldn't find a way to do a piecemeal Jest migration (since the tests either needed to be fully on Jest or fully in Tape), but if there is a better way, let me know.

@jgeewax jgeewax requested a review from rmothilal November 13, 2019 07:22
@jgeewax
Copy link
Contributor Author

jgeewax commented Nov 13, 2019

@rmothilal : The unit test check is failing because it now needs to run with jest, not tape + tape-xunit. I can make junit style output happen (via jest-junit), but we might want to change where the output goes and whatnot on the Circle CI configuration... How do you suggest we handle this one?

@rmothilal
Copy link

@rmothilal : The unit test check is failing because it now needs to run with jest, not tape + tape-xunit. I can make junit style output happen (via jest-junit), but we might want to change where the output goes and whatnot on the Circle CI configuration... How do you suggest we handle this one?

Hi @jgeewax, we currently have implementations of jest testing that use jest-junit and have the appropriate configuration in the circleci config. I also noticed that you have test config out of the test folder i would recommend creating a base folder in test and storing all appropriate config in there.

Please see examples of jest testing in the following repos:
https://github.com/mojaloop/account-lookup-service
https://github.com/mojaloop/transaction-requests-service

Please follow a similar pattern and use similar dependencies where possible.
Let me know if you require further information or assistance

@jgeewax
Copy link
Contributor Author

jgeewax commented Nov 13, 2019

That's fantastic -- thanks so much for the pointer! I'll go try to make this repo look more like those.

@jgeewax jgeewax force-pushed the jest branch 3 times, most recently from 208f44a to 5ac16d0 Compare November 14, 2019 06:26
@jgeewax
Copy link
Contributor Author

jgeewax commented Nov 14, 2019

@rmothilal : OK -- all updated to look like the others. Let me know if I missed anything?

@@ -0,0 +1,153 @@
/*****
Copy link
Contributor

@vgenev vgenev Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do not need this producer. As far as I remember this service only consumes so we can remove it. Sorry for leaving the producer.js- there :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK - I can remove this if you're sure it's not worth keeping 👍

Copy link

@rmothilal rmothilal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jgeewax i do not understand the massive rewrite of code for jest testing.
Please see comments, there are more to come, just didnt want to put too many in one review.
I also think that once you bring in the central-services-stream library it should greatly reduce the size of the PR

Comment on lines +1 to +153
_listOfProducers = {}
}

/**
* @function produceMessage
*
* @param {string} messageProtocol - message being created against topic
* @param {object} topicConf - configuration for the topic to produce to
* @param {object} config - Producer configuration, eg: to produce batch or
* poll
*
* @description Creates a producer on Kafka for the specified topic and
* configuration
*
* @returns {boolean} - returns true if producer successfully created and
* producers to
* @throws {error} - if not successfully create/produced to
*/
static async produceMessage (messageProtocol, topicConf, config) {
try {
let producer
if (this.listOfProducers[topicConf.topicName]) {
producer = this.listOfProducers[topicConf.topicName]
} else {
Logger.info('Producer::start::topic=' + topicConf.topicName)
producer = new Producer(config)
Logger.info('Producer::connect::start')
await producer.connect()
Logger.info('Producer::connect::end')
this.listOfProducers[topicConf.topicName] = producer
}
Logger.info(`Producer.sendMessage:: messageProtocol:'
${JSON.stringify(messageProtocol)}'`)
await producer.sendMessage(messageProtocol, topicConf)
Logger.info('Producer::end')
return true
} catch (e) {
Logger.error(e)
Logger.info('Producer error has occurred')
throw e
}
}

/**
* @function disconnect
*
* @param {string} topicName - Producer of the specified topic to be
* disconnected. If this is null, then ALL
* producers will be disconnected.
* Defaults: null.
*
* @description Disconnects a specific producer, or ALL producers from Kafka
*
* @returns {object} Promise
*/
static async disconnect (topicName = null) {
if (topicName && typeof topicName === 'string') {
await this.getProducer(topicName).disconnect()
} else if (topicName === null) {
let isError = false
const errorTopicList = []

let tpName
for (tpName in this.listOfProducers) {
try {
await this.getProducer(tpName).disconnect()
} catch (e) {
isError = true
errorTopicList.push({ topic: tpName, error: e.toString() })
}
}
if (isError) {
throw Error('The following Producers could not be disconnected: ' +
JSON.stringify(errorTopicList))
}
} else {
throw Error(`Unable to disconnect Producer: ${topicName}`)
}
}

/**
* @function getProducer
*
* @param {string} topicName - the topic name to locate a specific producer
*
* @description This is used to get a producer with the topic name to send
* messages to a Kafka topic
*
* @returns {Producer} - Returns consumer
* @throws {Error} - if consumer not found for topic name
*/
static getProducer (topicName) {
if (this.listOfProducers[topicName]) {
return this.listOfProducers[topicName]
} else {
throw Error(`No producer found for topic ${topicName}`)
}
}
}
module.exports = _Producer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this functionality is already in the central-services-stream library.
Please use that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not produce in that module, so this file can be removed. Sorry for me haven't done that. :/

Comment on lines +248 to +277
/**
* @function isConnected
*
* @param {string} topicName - the topic name of the consumer to check
*
* @description Use this to determine whether or not we are connected to the
* broker. Internally, it calls `getMetadata` to determine
* if the broker client is connected.
*
* @returns {true} - if connected
* @throws {Error} - if consumer can't be found or the consumer isn't connected
*/
static async isConnected (topicName) {
// This "module.exports" thing is important for mocking in the tests.
// TODO: Rearrange this file so that we don't need to do this.
const consumer = this.getConsumer(topicName)
const getMetadata = util.promisify(consumer.getMetadata)
const metadata = await getMetadata({
topicName,
timeout: 3000
})
const foundTopics = metadata.topics.map(topic => topic.name)
if (foundTopics.indexOf(topicName) === -1) {
Logger.debug(`Connected to consumer, but ${topicName} not found.`)
throw ErrorHandler.Factory.createInternalServerFSPIOPError(
`Connected to consumer, but ${topicName} not found.`)
}

return true
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this functionality is in central-services-stream library please use that

autoCommitEnabled,
connectedTimeStamp
})
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this functionality is catered for in the central-services-stream library. please use that functionality

Comment on lines +50 to +69
/**
* @function getConsumerMetadata
*
* @param {string} topicName - the topic name to locate a specific consumer
*
* @description This is used to retrieve a consumer for a given topic name
*
* @returns {Consumer} - Returns the registered consumer for a given topic name
* @throws {Error} - if no consumer is found for a topic name
*/
static getConsumerMetadata (topicName) {
const metadata = this._topicConsumerMetadata
if (topicName in metadata) {
return metadata[topicName]
} else {
// If the topicName doesn't exist in the map, throw an error.
throw ErrorHandler.Factory.createInternalServerFSPIOPError(
`No consumer found for topic ${topicName}`)
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is catered for in the central-services-stream library, please use that fucntionality

*/
static getConsumer (topicName) {
// This is just here for legacy reasons
return this.getKafkaConsumer(topicName)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this functionality is catered for by the central-services-stream library, please use that functionality

Copy link
Contributor

@vgenev vgenev Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can see here how to set the consumer with the latest libraries https://github.com/mojaloop/event-stream-processor/blob/master/src/setup.js

"test:unit": "jest --testMatch '**/test/unit/**/*.test.js'",
"test:coverage": "jest --coverage --coverageThreshold='{}' --testMatch '**/test/unit/**/*.test.js'",
"test:coverage-check": "jest --coverage --testMatch '**/test/unit/**/*.test.js'",
"test:junit": "jest --reporters=default --reporters=jest-junit --testMatch '**/test/unit/**/*.test.js'",
"audit:resolve": "SHELL=sh resolve-audit && sed -i 's/: \"^/: \"/' package.json",
"audit:check": "SHELL=sh check-audit"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add the following below audit:check

"dep:check": "npx ncu -e 2", "dep:update": "npx ncu -u"

"test:unit": "jest --testMatch '**/test/unit/**/*.test.js'",
"test:coverage": "jest --coverage --coverageThreshold='{}' --testMatch '**/test/unit/**/*.test.js'",
"test:coverage-check": "jest --coverage --testMatch '**/test/unit/**/*.test.js'",
"test:junit": "jest --reporters=default --reporters=jest-junit --testMatch '**/test/unit/**/*.test.js'",
"audit:resolve": "SHELL=sh resolve-audit && sed -i 's/: \"^/: \"/' package.json",
"audit:check": "SHELL=sh check-audit"
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add the following after the scripts object.

"pre-commit": [ "standard", "dep:check", "test" ],

"nodemailer-mock": "1.4.3",
"nodemon": "1.19.1",
"npm-audit-resolver": "1.5.0",
"npm-run-all": "4.1.5",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please install the dev dependency
npm-check-updates

* @returns {Array<string>} - list of topics
*/
static getListOfTopics () {
return Object.keys(this._topicConsumerMetadata)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this functionality is catered for by the central-services-stream library, please use that functionality

vgenev
vgenev previously approved these changes Nov 14, 2019
Copy link
Contributor

@vgenev vgenev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity why made everything Classes :)

@vgenev vgenev dismissed their stale review November 14, 2019 13:20

Rajiv has a point for the consumer

@jgeewax
Copy link
Contributor Author

jgeewax commented Nov 15, 2019

@rmothilal : I just wanted to clarify that my goal was to move to Jest without changing any code except tests, however I ran into a couple of issues with that:

  1. The test coverage was not up to the requirement (90% is what we have generally). This meant that I needed to write additional tests for modules.
  2. Some files were not tested at all, and others were tested but not actively run, so enabling them to run meant there was quite a lot of fixing to do.
  3. Quite a few modules used rewire and proxyquire, which don't appear to be supported by Jest. As a result, I figured the best option would be to refactor these modules (mostly collections of functions with a single exported function) into static classes so that the exported method signature and functionality remained the same, but the tests were easier to write using sinon for stubbing and spying.

So this leads me to the obvious question, which of the following options sounds best to you:

  1. Submit one big PR that switches us over to using central-services for common functionality as well as using Jest for testing.
  2. Submit a PR that uses Jest for testing, then another PR that switches us to central-services.
  3. Submit a PR that switches us to central-services, then another PR that uses Jest for testing.

I suspect Option 2 will be the quickest (and probably safest, given the new test coverage that gets added in this PR), however the other options are totally doable as well, so I just wanted to confirm with you before getting back to work.

@rmothilal
Copy link

@rmothilal : I just wanted to clarify that my goal was to move to Jest without changing any code except tests, however I ran into a couple of issues with that:

  1. The test coverage was not up to the requirement (90% is what we have generally). This meant that I needed to write additional tests for modules.
  2. Some files were not tested at all, and others were tested but not actively run, so enabling them to run meant there was quite a lot of fixing to do.
  3. Quite a few modules used rewire and proxyquire, which don't appear to be supported by Jest. As a result, I figured the best option would be to refactor these modules (mostly collections of functions with a single exported function) into static classes so that the exported method signature and functionality remained the same, but the tests were easier to write using sinon for stubbing and spying.

So this leads me to the obvious question, which of the following options sounds best to you:

  1. Submit one big PR that switches us over to using central-services for common functionality as well as using Jest for testing.
  2. Submit a PR that uses Jest for testing, then another PR that switches us to central-services.
  3. Submit a PR that switches us to central-services, then another PR that uses Jest for testing.

I suspect Option 2 will be the quickest (and probably safest, given the new test coverage that gets added in this PR), however the other options are totally doable as well, so I just wanted to confirm with you before getting back to work.

Hey @jgeewax I would believe that switching to central-services first and then doing the testing for jest would result in much less code being created and saving of lots of time. So option 3 would be my guess

@jgeewax
Copy link
Contributor Author

jgeewax commented Nov 18, 2019

OK - cool. Let's put this one on hold then. I'll start going through what central services has on hand, port this to use that while adding more tape tests for that, and then move to jest once we have that covered.

@rmothilal
Copy link

@jgeewax I have located the PR, maybe if you can pull in changes from master, update your branch and then i can review and approve

@lewisdaly lewisdaly closed this Feb 19, 2020
@lewisdaly
Copy link
Contributor

Accidentally closed.

@lewisdaly lewisdaly reopened this Feb 19, 2020
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants