rladies / meetupr

R interface to the meetup.com API
https://rladies.github.io/meetupr
MIT License
76 stars 25 forks source link

Add a test that we return the correct number of results #112

Open ledell opened 3 years ago

ledell commented 3 years ago

This is a bug that could have been found by having a unit test for this. This ticket is generic, though the bug above is for find_groups() specficially.

GregSutcliffe commented 3 years ago

I took a pass at this but I'm struggling to get the tests to use my existing meetup_token so that I can record the response with vcr.

Here's my test:

test_that("find_groups() can correctly get 2 pages", {
  vcr::use_cassette("find_groups_pages_2", {
    meetup_auth(token = '/path/to/my/meetupr/.httr-oauth.token')
    groups <- find_groups(text = "Ansible", radius = 'global')
  })
  expect_equal(nrow(groups), 324)
})

Which should work (it's what you'd do in the console). But the test fails, reporting:

── Error (test-find_groups2.R:5:3): find_groups() can correctly get 2 pages ────
Error: HTTP 400 Bad Request error encountered for: find/groups.

And when I look at the recorded YAML from vcr, I see (edited for brevity):

- request:
    method: get
    uri: https://api.meetup.com/find/groups?offset=0&text=Ansible&topic_id=&fields=&radius=global
  response:
    status:
      status_code: 400
      category: Client error
      reason: Bad Request
      message: 'Client error: (400) Bad Request'
    body:
      encoding: UTF-8
      file: no
      string: '{"errors":[{"code":"authentication_error","message":"Authenticated
        member required"}]}'

What am I doing wrong?

GregSutcliffe commented 3 years ago

To be clear, obviously I'll be removing the call to meetup_auth() once I have the recorded yaml, I just need a way to authenticate :)

Also, I just tried copying my token to the default path (mycheckout/.httr-oauth) and removing the call to meetup_auth() - still fails.

maelle commented 3 years ago

I think you are bitten by https://github.com/rladies/meetupr/blob/c9c584335377966c7c9e08534089f84d921cff50/tests/testthat/setup.R#L1 (so not vcr)

To contribute and add tests the best thing is to create a token for tests, now I am wondering what would be the best way to make you add one. I'm looking at https://github.com/ropensci/rtweet/pull/512

maelle commented 3 years ago

that said meetup_token() in that setup file should use your token :thinking:

GregSutcliffe commented 3 years ago

Hmm, I added some classic oldschool print statements when running local tests and was fairly sure I wasn't hitting that branch, but rather the else branch. And thus, yes, it should read my token. I will dig a bit more, maybe add some debug in meetup_token()...

maelle commented 3 years ago

thank you! there's definitely a bug somewhere in our codebase.

GregSutcliffe commented 3 years ago

@maelle OK, making progress.

So, this is definitely hitting the else {} branch, which sets MEETUPR_TESTING and then it descends into

https://github.com/rladies/meetupr/blob/fb84cec1fc101ed15c93a49fcfeb046873fabfd9/R/meetup_auth.R#L208-L211

which clearly skips any kind of token loading. So it's no surprise I can't auth.

I commented that out, which brings us to:

https://github.com/rladies/meetupr/blob/fb84cec1fc101ed15c93a49fcfeb046873fabfd9/R/meetup_auth.R#L212-L213

And here I get stuck. With some print statements I can see it's trying to load a default token from "~/.local/share/meetupr/meetupr-token.rds" which obviously doesn't exist. I changed it to look like this for debugging:

  meetup_auth(token = '~/path/to/my/real/.httr-oauth', verbose = T)
  message(token_available())
  httr::config(token = .state$token)
}

Which claims to load the token - you can see the verbose output of meetup_auth and token_available:

HttrAdapter enabled!
net connect allowed
<Token>
<oauth_endpoint>
 authorize: https://secure.meetup.com/oauth2/authorize
 access:    https://secure.meetup.com/oauth2/access
<oauth_app> meetup
  key:    <redacted>
  secret: <hidden>
<credentials> access_token, refresh_token, token_type, expires_in
---
TRUE
[ FAIL 1 | WARN 0 | SKIP 0 | PASS 0 ]

However, it fails. But differently, now I get a 401 Unauthorized instead of a 400. So, progress I guess? :)

Is there a way to record an interactive session with vcr? I feel that would be way easier, as my token works fine in the console....

maelle commented 3 years ago

Thank you!

Where is your token? The location With some print statements I can see it's trying to load a default token from "~/.local/share/meetupr/meetupr-token.rds" which obviously doesn't exist. is the new default location.

I'll come back to this within the next few days.

GregSutcliffe commented 3 years ago

So I've been doing quite a lot of digging. Part of the problem is the code creating (broken) tokens which are then hidden from me because I use "git status" to see what I've touched in the repo, but .httr-oauth is in the .gitignore, so I don't see them. I've found several that were not working and getting in the way - that didn't help.

However, I'm now fairly sure it's VCR that is breaking me. Here's my reproducer:

# put token in the default place
cp ~/path/to/my/token /home/greg/.local/share/meetupr/meetupr-token.rds

# bypass the testing environment check
--- a/tests/testthat/setup.R
+++ b/tests/testthat/setup.R
 } else {
-  Sys.setenv("MEETUPR_TESTING" = TRUE)
-  token <- meetup_token()
+ # Sys.setenv("MEETUPR_TESTING" = TRUE)
+  token <- meetup_token(TRUE)
 }

# Write a test that doesnt use vcr
test_that("find_groups() can correctly get 2 pages", {
  groups <- find_groups(text = "Ansible", radius = 'global')
  expect_equal(nrow(groups), 323)
})

This passes, which is a relief:

==> Testing R file using 'testthat'

Loading meetupr
A .httr-oauth file exists in current working directory.
When/if needed, the credentials cached in .httr-oauth will be used for this session.
Or run meetup_auth() for explicit authentication and authorization.

══ Testing test-find_groups2.R ═════════════════════════════════════════════════
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 0 ]Auto-refreshing stale OAuth token.
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 1 ] Done!

Test complete

If I now enable VCR:

test_that("find_groups() can correctly get 2 pages", {
  vcr::use_cassette("find_groups_pages_2", {
    groups <- find_groups(text = "Ansible", radius = 'global')
  })
  expect_equal(nrow(groups), 323)
})

I go back to getting a 401. I wondered if it's related to this:

https://github.com/rladies/meetupr/blob/fb84cec1fc101ed15c93a49fcfeb046873fabfd9/tests/testthat/setup.R#L29-L35

But I think that's for filtering the record after the call is complete, right? In any case, I'm now stuck - it seems that the call the vcr is the issue, and I'm not clear on how to debug it.

GregSutcliffe commented 3 years ago

OK, more progress - I figured out how to use vcr on the console to do the recording. So, now I can easily record a YAML with something like this:

devtools::load_all()
meetup_auth()
token <- .state$token
# copied from tests/setup.R
library("vcr")
invisible(vcr::vcr_configure(
  filter_request_headers = list(Authorization = "not my bearer token"),
  filter_sensitive_data = list(
    "<<<my_refresh_token>>>" = token$credentials$refresh_token %||% "lala"
  ),
  dir = here::here("tests/fixtures")
))

use_cassette('greg_test',{
  find_groups('Ansible')
})

This correctly runs, and creates a YAML recording, but it looks like its not capturing the second page, so then I get:

An HTTP request has been made that vcr does not know how to handle:
GET https://api.meetup.com/find/groups?page=200&text=Ansible&radius=global&topic_id=&offset=1&fields=?offset=0
vcr is currently using the following cassette:
  - ../fixtures/find_groups_2_pages.yml
    - record_mode: once
    - match_requests_on: method, uri

Note the page=200 and offset=1, this is the second call in the pagination loop. So progress, I can record something, and get the test to read it. But I'm out of time for today, will poke it more tomorrow ;)

GregSutcliffe commented 3 years ago

@maelle I think I got it, but I'm certain I'm making this harder than it should be. I've got working tests, and I've added them to #111

To get there, I had to work out how to get vcr to run in my console in record mode. Then once I had the yaml, the tests were trivial. To do that, I used this script at the console:

# load meetup
devtools::load_all()

# read my token
meetup_auth(token = '~/.local/share/meetupr/meetupr-token.rds')

# This is copied from setup.R so that VCR deletes sensitive data and writes to the fixtures
library("vcr")
token <- .state$token
invisible(vcr::vcr_configure(
  filter_request_headers = list(Authorization = "not my bearer token"),
  filter_sensitive_data = list(
    "<<<my_refresh_token>>>" = token$credentials$refresh_token %||% "lala"
  ),
  dir = here::here("tests/fixtures")
))

# for some reason i have to run at least one API call outside of vcr, else I get 401s
find_groups('Ansible')

# now I can record the transaction
use_cassette('find_groups_2_pages', record = 'all', {
  find_groups('Ansible')
})

With that, I managed to create two passing tests in #111. Is there a better way?

maelle commented 3 years ago

@GregSutcliffe I'm working on a PR where I change the place of the environment variable that as you correctly pointed out was preventing the addition of new test in the absence of MEETUPR_PWD. I am so sorry and also very thankful that you found this out!

GregSutcliffe commented 3 years ago

No problem @maelle I learned a lot about vcr along the way and that's going to be useful at some point. No learning is ever wasted :)