Skip to content
This repository has been archived by the owner on Feb 13, 2024. It is now read-only.

Add an end to end test case. #134

Merged
merged 1 commit into from
Nov 17, 2017
Merged

Add an end to end test case. #134

merged 1 commit into from
Nov 17, 2017

Conversation

f2prateek
Copy link
Contributor

@f2prateek f2prateek commented Nov 15, 2017

No description provided.

@f2prateek
Copy link
Contributor Author

This is a quick first pass so let me know if you have any suggestions.

I couldn't get await working here out of the box (Parsing error: The keyword 'await' is reserved), the code is a bit clunky.

Also if any of the callbacks fail, the test seems to hang forever, likely due to avajs/ava#567.

@f2prateek
Copy link
Contributor Author

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.
avajs/ava#934

test.js Outdated
.then(function (response) {
t.is(response.status, 200)

// TODO: fail and end the test if none of the requests match.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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 () {
Copy link
Contributor

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.

Copy link
Contributor Author

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".

Copy link
Contributor

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()
}

Choose a reason for hiding this comment

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

better:

const testFn = async () => {

}

Copy link
Contributor Author

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 }
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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

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.

Copy link

@vadimdemedes vadimdemedes Nov 15, 2017

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

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.

Copy link
Contributor

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')
Copy link
Contributor

Choose a reason for hiding this comment

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

What is “zfte7jmy76oz”?

Copy link
Contributor Author

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.

Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

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()
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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? 😉

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@vadimdemedes
Copy link

vadimdemedes commented Nov 15, 2017

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.

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 => {

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.

Copy link
Contributor Author

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({

Choose a reason for hiding this comment

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

var => const

Copy link
Contributor Author

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 }

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}` }

Copy link
Contributor Author

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}`

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 }

Copy link
Contributor Author

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 () {

Choose a reason for hiding this comment

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

Use arrow functions.

Copy link
Contributor Author

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 => {
Copy link
Contributor

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
  }
}

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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'
    })

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.

Copy link
Contributor Author

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
      }
    }
> analytics-node@3.1.1 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.

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 => {
Copy link
Contributor

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.

Copy link
Contributor

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)
}

Copy link
Contributor Author

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.

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.

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?

Copy link
Contributor Author

@f2prateek f2prateek Nov 15, 2017

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?

Choose a reason for hiding this comment

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

send event to runscope

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@f2prateek f2prateek mentioned this pull request Nov 15, 2017
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"

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.

Copy link
Contributor Author

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: {

Choose a reason for hiding this comment

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

=> properties: { id }

Copy link
Contributor Author

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.

Choose a reason for hiding this comment

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

Copy link
Contributor Author

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 :)

Copy link
Contributor Author

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}` }

Choose a reason for hiding this comment

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

'Authorization' => authorization (without quotes)

Copy link
Contributor Author

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?

Copy link
Contributor

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) {

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated

@f2prateek f2prateek force-pushed the e2e-test branch 3 times, most recently from 6483a5e to 72c1315 Compare November 15, 2017 18:56
@f2prateek f2prateek force-pushed the e2e-test branch 4 times, most recently from 307243e to 08fc33e Compare November 15, 2017 19:14
@f2prateek
Copy link
Contributor Author

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.

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

Successfully merging this pull request may close these issues.

3 participants