-
Notifications
You must be signed in to change notification settings - Fork 150
Add an end to end test case. #134
Conversation
This is a quick first pass so let me know if you have any suggestions. I couldn't get Also if any of the callbacks fail, the test seems to hang forever, likely due to avajs/ava#567. |
Given the number of external systems this calls out to, it'd be nice if there was a way to retry the test if a single run failed, and only fail if say 3/3 runs failed. Not easily doable with AVA atm. |
test.js
Outdated
.then(function (response) { | ||
t.is(response.status, 200) | ||
|
||
// TODO: fail and end the test if none of the requests match. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could set a timeout on the client, which would cause an error and result in failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why the timeout on the client would help? The requests won't match if the library had a bug,api was down, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently the test will just hang forever if this happens
I'm suggesting we call .end()
after a specified amount of time. One way to do this would be to have Axios throw an error after N seconds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the async stuff fixed this!
test.js
Outdated
test.cb('e2e', t => { | ||
const id = uid(16) | ||
|
||
const testFn = function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you make this an async function
, you’ll be able to await
promises.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this project is setup for async/await #134 (comment).
I tried:
const response = await axios.get(....)
And got the error: "Parsing error: The keyword 'await' is reserved".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, you need to make the testFn
async:
const testFn = async function () {
await somePromise()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better:
const testFn = async () => {
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed now.
test.js
Outdated
var axiosClient = axios.create({ | ||
baseURL: 'https://api.runscope.com', | ||
timeout: 10 * 1000, | ||
headers: { 'Authorization': 'Bearer ' + process.env.RUNSCOPE_TOKEN } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m worried about the implications this will have on open-source contributions, as folks won’t have access to this token.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the best we can do here is to run this only on CI? If we don't automate this I'm sure we'll forget to run this test before releasing/making big changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I can't inline the token as it doesn't seem to be very well scoped. Though maybe we could setup a dedicated account for this and it wouldn't matter like we do for SauceLabs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps moving this test out of the "normal" test suite and only having CI run it would solve the problem?
In package.json
, we could do:
{
"scripts": {
"test": "ava test.js",
"test-e2e": "ava e2e/test.js
}
}
And in circle.yml
:
test:
override:
- yarn test
- yarn test-e2e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating new folders and files, I propose we leave this test in test.js
and use is-ci to determine whether to run it or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not about lines of code, but not doing something that's already been handled perfectly by other people. Left-pad argument doesn't apply anymore, since npm took measures to prevent situations like that in the future. See sindresorhus/ama#10 (comment) for a much more extensive answer to this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still doesn't make sense to me to to pull in 50+ lines of dependencies when a one-liner does the job for us. I understand the flexibility is-ci/ci-info will offer, but seems overkill if we don't need that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I'll put a pin in in this and use is-ci if that's what you'll want here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a compromise, I propose we ditch CI check altogether. Instead, let's use something more descriptive of what's actually happening:
if (process.env.INTEGRATION) {
test(...)
}
$ INTEGRATION=true npm test
There should probably be a better name than INTEGRATION
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I very much prefer being explicit here and thought having to CI=true yarn test
would have been awkward.
test.js
Outdated
|
||
retries(axiosClient, { retries: 3 }) | ||
|
||
axiosClient.get('buckets/zfte7jmy76oz/messages?count=10') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is “zfte7jmy76oz”?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the ID for our Runscope bucket. I'll link to it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh ok, the a link would be great. 👍
Maybe a comment too?
test.js
Outdated
id: `${id}` | ||
} | ||
}, function () { | ||
setTimeout(testFn, 5000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this timeout for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latency between sending to api.segment.io and it being received by an integration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. Maybe we should explain that in a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could it be shorter?
test.js
Outdated
}, function () { | ||
setTimeout(testFn, 5000) | ||
}) | ||
analytics.flush() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this .flush()
? It looks like we tell the library to flush after a single call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not, I didn't totally trust the library to do the right thing here so I added both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't totally trust the library
That's not good. Let's chat about that on Slack.
Maybe you should remove it to gain trust in the library? 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep I can change that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left the flush but removed the flushAt:1 part. Can reverse that if we want.
You can use the excellent async-retry for it. |
test.js
Outdated
@@ -447,3 +450,60 @@ test('alias - require previousId and userId', t => { | |||
}) | |||
}) | |||
}) | |||
|
|||
test.cb('e2e', t => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should make this test async
and remove cb
mode. All of the code in it can be converted to async/await
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
test.js
Outdated
const id = uid(16) | ||
|
||
const testFn = function () { | ||
var axiosClient = axios.create({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var => const
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
Should this be a linter check?
test.js
Outdated
var axiosClient = axios.create({ | ||
baseURL: 'https://api.runscope.com', | ||
timeout: 10 * 1000, | ||
headers: { 'Authorization': 'Bearer ' + process.env.RUNSCOPE_TOKEN } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
headers: { 'Authorization': 'Bearer ' + process.env.RUNSCOPE_TOKEN }
=> headers: { authorization: `Bearer ${process.env.RUNSCOPE_TOKEN}` }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
test.js
Outdated
userId: 'prateek', | ||
event: 'E2E Test', | ||
properties: { | ||
id: `${id}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String is pointless here, can just be { id }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
test.js
Outdated
properties: { | ||
id: `${id}` | ||
} | ||
}, function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use arrow functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed with the async stuff.
test.js
Outdated
|
||
let found = false | ||
|
||
messagesResponse.data.data.forEach(async item => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could make this a simple for
loop and only iterate until we "find" what we're looking for:
const {data} = messagesResponse.data
for (const item of data) {
const response = await axiosClient.get(`buckets/zfte7jmy76oz/messages/${item.uuid}`)
t.is(response.status, 200)
const body = JSON.parse(response.data.data.request.body)
if (id === body.properties.id) {
found = true
break
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it should be that way, because forEach()
doesn't care about async functions. Meaning, it will not wait until those functions finish.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
test.js
Outdated
const response = await axiosClient.get('buckets/zfte7jmy76oz/messages/' + item.uuid) | ||
t.is(response.status, 200) | ||
|
||
const body = JSON.parse(response.data.data.request.body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Axios will return JSON for us if we ask it to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do I get it to do that? I tried:
const response = await axiosClient.get(`buckets/zfte7jmy76oz/messages/${item.uuid}`, {
responseType: 'json'
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try passing it in the options when creating axiosClient
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still failed.
const axiosClient = axios.create({
baseURL: 'https://api.runscope.com',
timeout: 10 * 1000,
headers: { Authorization: `Bearer ${process.env.RUNSCOPE_TOKEN}` },
responseType: 'json'
})
for (const item of messagesResponse.data.data) {
const response = await axiosClient.get(`buckets/zfte7jmy76oz/messages/${item.uuid}`)
t.is(response.status, 200)
if (id === response.data.data.request.body.properties.id) {
found = true
break
}
}
> [email protected] test /Users/prateek/dev/src/github.com/segmentio/analytics-node
> standard && ava
30 passed
1 failed
end to end test
/Users/prateek/dev/src/github.com/segmentio/analytics-node/test.js:491
490:
491: if (id === response.data.data.request.body.properties.id) {
492: found = true
Rejected promise returned by test. Reason:
TypeError {
message: 'Cannot read property \'id\' of undefined',
}
Test.<anonymous> (test.js:491:18)
step (test.js:52:191)
npm ERR! Test failed. See above for more details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error is different here, properties
is undefined.
test.js
Outdated
@@ -447,3 +451,50 @@ test('alias - require previousId and userId', t => { | |||
}) | |||
}) | |||
}) | |||
|
|||
test('e2e', async t => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think this should only run in certain conditions.
Maybe do something like:
const { RUN_E2E_TESTS } = process.env
if (RUN_E2E_TESTS) {
test('e2e', async t => { ... })
}
And in circle.yml
, add RUN_E2E_TESTS=true
in the "environment" config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid nesting, you can do this:
async function e2etest (t) {
const id = uid(16)
// ...
}
const { RUN_E2E_TESTS } = process.env
if (RUN_E2E_TESTS) {
test('e2e', e2etest)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, didn't get around to that yet. Will update on the original comment when I do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nesting here is totally fine, looks more natural.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@f2prateek could you also modify test title to be more descriptive?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, do you have any other suggestions for naming?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
send event to runscope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the description and added some comments, let me know if that looks ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
package.json
Outdated
@@ -51,6 +51,7 @@ | |||
"nsp": "^2.6.3", | |||
"pify": "^3.0.0", | |||
"sinon": "^2.3.8", | |||
"standard": "^9.0.1" | |||
"standard": "^9.0.1", | |||
"await-sleep": "^0.0.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use delay
instead, it's high quality and widely used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
test.js
Outdated
analytics.track({ | ||
userId: 'prateek', | ||
event: 'E2E Test', | ||
properties: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=> properties: { id }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should be a thing that should be flagged by the style guide/standard. If it didn't, i don't think it's worth bothering with those changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's about consistency. Here it's on multiple lines and on the other ones it's on one (https://github.com/segmentio/analytics-node/pull/134/files#diff-1dd241c4cd3fd1dd89c570cee98b79ddR474, https://github.com/segmentio/analytics-node/pull/134/files#diff-1dd241c4cd3fd1dd89c570cee98b79ddR476).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's okay. The point of code formatter is to not have to make these kinds of changes by hand. If a style guide doesn't flag it, I think it's okay to leave it as creative expression :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(though I did update it here as I was making other changes at the same time).
test.js
Outdated
const axiosClient = axios.create({ | ||
baseURL: 'https://api.runscope.com', | ||
timeout: 10 * 1000, | ||
headers: { 'Authorization': `Bearer ${process.env.RUNSCOPE_TOKEN}` } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'Authorization'
=> authorization
(without quotes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why's that? it should be capitalized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HTTP headers are case insensitive.
test.js
Outdated
|
||
let found = false | ||
|
||
for (const item of messagesResponse.data.data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look good tbh. Much better:
const requests = messagesResponse.data.data.map(async item => {
const res = await axiosClient.get(`buckets/zfte7jmy76oz/messages/${item.uuid}`)
return JSON.parse(res.data.data.request.body)
})
const messages = await Promise.all(requests)
const message = messages.find(message => message.properties.id === id)
t.truthy(message)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
6483a5e
to
72c1315
Compare
307243e
to
08fc33e
Compare
I updated some comments to better describe the test for non-Segment contributors. I would like to add retries but blocked by the errors in #136. For now I'll merge this as is and can re-prioritize retries in the future if this ends up being a flaky test. |
No description provided.