예제 #1
0
def test_get_client_credentials_token(request_ctx):
    """Test retrieval of the webhook tokens."""
    with request_ctx("/"), patch("orcid_hub.utils.requests.post") as mockpost:
        admin = User.get(email="*****@*****.**")
        org = admin.organisation
        login_user(admin)
        mockresp = MagicMock(status_code=201)
        mockresp.json.return_value = {
            "access_token": "ACCESS-TOKEN-123",
            "token_type": "bearer",
            "refresh_token": "REFRESH-TOKEN-123",
            "expires_in": 99999,
            "scope": "/webhook",
            "orcid": None,
        }
        mockpost.return_value = mockresp

        OrcidToken.create(
            org=org, access_token="access_token", refresh_token="refresh_token", scopes="/webhook"
        )
        token = utils.get_client_credentials_token(org, "/webhook")
        assert (
            OrcidToken.select()
            .where(OrcidToken.org == org, OrcidToken.scopes == "/webhook")
            .count()
            == 1
        )
        assert token.access_token == "ACCESS-TOKEN-123"
        assert token.refresh_token == "REFRESH-TOKEN-123"
        assert token.expires_in == 99999
        assert token.scopes == "/webhook"
예제 #2
0
def test_test_database(models):
    """Test of the consitency of the test database."""
    assert Organisation.select().count() == 10
    assert User.select().count() == 63
    assert OrcidToken.select().count() == 60
    assert AffiliationRecord.select().count() == 10
    assert FundingRecord.select().count() == 10
    assert FundingContributor.select().count() == 10
    assert FundingInvitee.select().count() == 10
    assert ExternalId.select().count() == 10
    assert WorkRecord.select().count() == 10
    assert WorkContributor.select().count() == 10
    assert WorkExternalId.select().count() == 10
    assert WorkInvitee.select().count() == 10
    assert PeerReviewRecord.select().count() == 10
    assert PeerReviewExternalId.select().count() == 10
    assert PeerReviewInvitee.select().count() == 10
    assert ResearcherUrlRecord.select().count() == 10
    assert OtherNameRecord.select().count() == 10
    assert KeywordRecord.select().count() == 10
    assert Task.select().count() == 30
    assert UserOrgAffiliation.select().count() == 30

    assert User.get(id=43).admin_for.count() == 10
    assert User.get(id=1).admin_for.count() == 0
    assert User.get(id=42).admin_for.count() > 0
    assert User.get(id=2).organisations.count() > 0
    assert Organisation.get(id=1).admins.count() == 1
    assert Organisation.get(id=5).users.count() > 0
    assert Organisation.get(id=5).admins.count() > 0
    assert User.select().where(User.orcid == User.get(
        email="*****@*****.**").orcid).count() == 3
    assert len(User.get(email="*****@*****.**").org_links) == 3

    user = User.get(email="*****@*****.**")
    available_organisations = user.available_organisations
    assert available_organisations.count() == 10

    admin = User.create(email="*****@*****.**", organisation=user.organisation, confirmed=True,
            first_name="TEST", last_name="ADMIN", roles=Role.ADMIN)
    ui = UserInvitation.create(email=user.email, invitee=user, inviter=admin, token="TOKEN-123")
    admin.delete_instance()
    ui = UserInvitation.get(ui.id)
    assert ui.inviter_id is None
    user.delete_instance()
    assert not UserInvitation.select().where(UserInvitation.id == ui.id).exists()

    org = Organisation.select().limit(1).first()
    user = User.select().limit(1).first()
    ot = OrcidToken.create(user=user, org=org, scope="S1,S2,S3")
    assert len(ot.scopes) == 3

    ot.scopes = ["A", "B", "C", "D"]
    assert ot.scope == "A,B,C,D"
예제 #3
0
def test_link_orcid_auth_callback_with_affiliation(name, mocker, client):
    """Test ORCID callback - the user authorized the organisation access to the ORCID profile."""
    mocker.patch("requests_oauthlib.OAuth2Session.fetch_token", lambda self, *args, **kwargs: dict(
        name="NEW TEST",
        access_token="ABC123",
        orcid="ABC-123-456-789",
        scope=['/read-limited,/activities/update'],
        expires_in="1212",
        refresh_token="ABC1235"))
    m = mocker.patch("orcid_hub.orcid_client.MemberAPI")
    mocker.patch("orcid_hub.orcid_client.SourceClientId")

    org = Organisation.get(name="THE ORGANISATION")
    test_user = User.create(
        name=name,
        email="*****@*****.**",
        organisation=org,
        orcid="ABC123",
        confirmed=True)

    UserOrg.create(user=test_user, org=org, affiliations=Affiliation.EMP | Affiliation.EDU)

    client.login(test_user)
    resp = client.get("/link")
    state = session['oauth_state']

    resp = client.get(f"/auth?state={state}")
    api_mock = m.return_value
    test_user = User.get(test_user.id)
    assert test_user.orcid == "ABC-123-456-789"

    orcid_token = OrcidToken.get(user=test_user, org=org)
    assert orcid_token.access_token == "ABC123"

    api_mock.create_or_update_affiliation.assert_has_calls([
        call(affiliation=Affiliation.EDU, initial=True),
        call(affiliation=Affiliation.EMP, initial=True),
    ])

    # User with no Affiliation, should get flash warning.
    user_org = UserOrg.get(user=test_user, org=org)
    user_org.affiliations = Affiliation.NONE
    user_org.save()
    orcid_token.delete_instance()

    assert OrcidToken.select().where(OrcidToken.user == test_user, OrcidToken.org == org).count() == 0
    resp = client.get(f"/auth?state={state}")
    assert resp.status_code == 302
    assert b"<!DOCTYPE HTML" in resp.data, "Expected HTML content"
    assert "profile" in resp.location, "redirection to 'profile' showing the ORCID"
    assert OrcidToken.select().where(OrcidToken.user == test_user, OrcidToken.org == org).count() == 1

    get_person = mocker.patch("requests_oauthlib.OAuth2Session.get", return_value=Mock(status_code=200))
    resp = client.get(f"/profile", follow_redirects=True)
    assert b"can create and update research activities" in resp.data
    get_person.assert_called_once()

    get_person = mocker.patch("requests_oauthlib.OAuth2Session.get", return_value=Mock(status_code=401))
    resp = client.get(f"/profile", follow_redirects=True)
    assert b"you'll be taken to ORCID to create or sign into your ORCID record" in resp.data
    get_person.assert_called_once()
예제 #4
0
def test_orcidtoken_count(test_models):
    assert OrcidToken.select().count() == 60
예제 #5
0
def test_webhook_registration(client):
    """Test webhook registration."""
    test_client = client
    user = User.get(email="*****@*****.**")
    test_client.login(user)
    org = user.organisation
    orcid_id = "0000-0000-0000-00X3"
    client = Client.get(org=org)

    resp = test_client.post(
        "/oauth/token",
        data=dict(
            grant_type="client_credentials",
            client_id=client.client_id,
            client_secret=client.client_secret,
            scope="/webhook",
        ),
    )

    assert resp.status_code == 200
    data = json.loads(resp.data)
    client = Client.get(client_id="CLIENT_ID")
    token = Token.select().where(Token.user == user, Token._scopes == "/webhook").first()
    assert data["access_token"] == token.access_token
    assert data["expires_in"] == test_client.application.config["OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"]
    assert data["token_type"] == token.token_type
    # prevously created access token should be removed

    resp = test_client.put(
        "/api/v1/INCORRECT/webhook/http%3A%2F%2FCALL-BACK",
        headers=dict(authorization=f"Bearer {token.access_token}"),
    )
    assert resp.status_code == 415
    assert json.loads(resp.data)["error"] == "Missing or invalid ORCID iD."

    resp = test_client.put(
        "/api/v1/0000-0001-8228-7153/webhook/http%3A%2F%2FCALL-BACK",
        headers=dict(authorization=f"Bearer {token.access_token}"),
    )
    assert resp.status_code == 404
    assert json.loads(resp.data)["error"] == "Invalid ORCID iD."

    resp = test_client.put(
        f"/api/v1/{orcid_id}/webhook/INCORRECT-WEBHOOK-URL",
        headers=dict(authorization=f"Bearer {token.access_token}"),
    )
    assert resp.status_code == 415
    assert json.loads(resp.data) == {
        "error": "Invalid call-back URL",
        "message": "Invalid call-back URL: INCORRECT-WEBHOOK-URL",
    }

    with patch("orcid_hub.utils.requests.post") as mockpost, patch(
        "orcid_hub.utils.requests.put"
    ) as mockput:
        # Access toke request resp:
        mockresp = MagicMock(status_code=201)
        mockresp.json.return_value = {
            "access_token": "ACCESS-TOKEN-123",
            "token_type": "bearer",
            "refresh_token": "REFRESH-TOKEN-123",
            "expires_in": 99999,
            "scope": "/webhook",
            "orcid": None,
        }
        mockpost.return_value = mockresp
        # Webhook registration response:
        mockresp = MagicMock(status_code=201, data=b"")
        mockresp.headers = {
            "Server": "TEST123",
            "Connection": "keep-alive",
            "Pragma": "no-cache",
            "Expires": "0",
        }
        mockput.return_value = mockresp
        resp = test_client.put(
            f"/api/v1/{orcid_id}/webhook/http%3A%2F%2FCALL-BACK",
            headers=dict(authorization=f"Bearer {token.access_token}"),
        )

        assert resp.status_code == 201
        args, kwargs = mockpost.call_args
        assert args[0] == "https://sandbox.orcid.org/oauth/token"
        assert kwargs["data"] == {
            "client_id": "APP-12345678",
            "client_secret": "CLIENT-SECRET",
            "scope": "/webhook",
            "grant_type": "client_credentials",
        }
        assert kwargs["headers"] == {"Accept": "application/json"}

        args, kwargs = mockput.call_args
        assert (
            args[0]
            == "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/http%3A%2F%2FCALL-BACK"
        )
        assert kwargs["headers"] == {
            "Accept": "application/json",
            "Authorization": "Bearer ACCESS-TOKEN-123",
            "Content-Length": "0",
        }

        q = OrcidToken.select().where(OrcidToken.org == org, OrcidToken.scopes == "/webhook")
        assert q.count() == 1
        orcid_token = q.first()
        assert orcid_token.access_token == "ACCESS-TOKEN-123"
        assert orcid_token.refresh_token == "REFRESH-TOKEN-123"
        assert orcid_token.expires_in == 99999
        assert orcid_token.scopes == "/webhook"

    with patch("orcid_hub.utils.requests.delete") as mockdelete:
        # Webhook deletion response:
        mockresp = MagicMock(status_code=204, data=b"")
        mockresp.headers = {
            "Seresper": "TEST123",
            "Connection": "keep-alive",
            "Location": "TEST-LOCATION",
            "Pragma": "no-cache",
            "Expires": "0",
        }
        mockdelete.return_value = mockresp
        resp = test_client.delete(
            f"/api/v1/{orcid_id}/webhook/http%3A%2F%2FCALL-BACK",
            headers=dict(authorization=f"Bearer {token.access_token}"),
        )
        assert resp.status_code == 204
        assert urlparse(resp.location).path == f"/api/v1/{orcid_id}/webhook/http://TEST-LOCATION"

        args, kwargs = mockput.call_args
        assert (
            args[0]
            == "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/http%3A%2F%2FCALL-BACK"
        )
        assert kwargs["headers"] == {
            "Accept": "application/json",
            "Authorization": "Bearer ACCESS-TOKEN-123",
            "Content-Length": "0",
        }

        q = OrcidToken.select().where(OrcidToken.org == org, OrcidToken.scopes == "/webhook")
        assert q.count() == 1
        token = q.first()
        assert token.access_token == "ACCESS-TOKEN-123"
        assert token.refresh_token == "REFRESH-TOKEN-123"
        assert token.expires_in == 99999
        assert token.scopes == "/webhook"
예제 #6
0
def test_org_webhook_api(client, mocker):
    """Test Organisation webhooks."""
    mocker.patch.object(
        utils.requests,
        "post",
        lambda *args, **kwargs: Mock(
            status_code=201,
            json=lambda: dict(
                access_token="ABC123", refresh_token="REFRESH_ME", expires_in=123456789
            ),
        ),
    )

    mockput = mocker.patch.object(utils.requests, "put")
    mockdelete = mocker.patch.object(utils.requests, "delete")

    org = client.data["org"]
    admin = org.tech_contact

    send_email = mocker.patch("orcid_hub.utils.send_email")

    api_client = Client.get(org=org)

    resp = client.post(
        "/oauth/token",
        data=dict(
            grant_type="client_credentials",
            client_id=api_client.client_id,
            client_secret=api_client.client_secret,
            scope="/webhook",
        ),
    )

    assert resp.status_code == 200
    data = json.loads(resp.data)
    api_client = Client.get(client_id="CLIENT_ID")
    token = Token.select().where(Token.user == admin, Token._scopes == "/webhook").first()

    assert data["access_token"] == token.access_token
    assert data["expires_in"] == client.application.config["OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"]
    assert data["token_type"] == token.token_type

    client.access_token = token.access_token

    resp = client.put("/api/v1/webhook/INCORRECT-WEBHOOK-URL")
    assert resp.status_code == 415
    assert json.loads(resp.data) == {
        "error": "Invalid call-back URL",
        "message": "Invalid call-back URL: INCORRECT-WEBHOOK-URL",
    }

    # Webhook registration response:
    mockresp = MagicMock(status_code=201, data=b"")
    mockresp.headers = {
        "Server": "TEST123",
        "Connection": "keep-alive",
        "Pragma": "no-cache",
        "Expires": "0",
    }
    mockput.return_value = mockresp

    # Webhook deletion response:
    mockresp = MagicMock(status_code=204, data=b"")
    mockresp.headers = {
        "Seresper": "TEST123",
        "Connection": "keep-alive",
        "Location": "TEST-LOCATION",
        "Pragma": "no-cache",
        "Expires": "0",
    }
    mockdelete.return_value = mockresp

    resp = client.put("/api/v1/webhook/http%3A%2F%2FCALL-BACK")
    assert resp.status_code == 200

    resp = client.put(
        "/api/v1/webhook/http%3A%2F%2FCALL-BACK",
        data=dict(enabled=True, url="https://CALL-BACK.edu/callback"),
    )
    assert resp.status_code == 201

    server_name = client.application.config["SERVER_NAME"]
    mockput.assert_has_calls(
        [
            call(
                "https://api.sandbox.orcid.org/1001-0001-0001-0001/webhook/"
                f"https%3A%2F%2F{server_name}%2Fservices%2F21%2Fupdated",
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer ABC123",
                    "Content-Length": "0",
                },
            ),
            call(
                "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/"
                f"https%3A%2F%2F{server_name}%2Fservices%2F22%2Fupdated",
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer ABC123",
                    "Content-Length": "0",
                },
            ),
            call(
                "https://api.sandbox.orcid.org/0000-0000-0000-11X2/webhook/"
                f"https%3A%2F%2F{server_name}%2Fservices%2F30%2Fupdated",
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer ABC123",
                    "Content-Length": "0",
                },
            ),
        ]
    )

    q = OrcidToken.select().where(OrcidToken.org == org, OrcidToken.scopes == "/webhook")
    assert q.exists()
    assert q.count() == 1
    orcid_token = q.first()
    assert orcid_token.access_token == "ABC123"
    assert orcid_token.refresh_token == "REFRESH_ME"
    assert orcid_token.expires_in == 123456789
    assert orcid_token.scopes == "/webhook"

    # deactivate:

    resp = client.delete("/api/v1/webhook/http%3A%2F%2FCALL-BACK")
    assert resp.status_code == 200
    assert "job-id" in resp.json

    # activate with all options:
    mockput.reset_mock()
    resp = client.put(
        "/api/v1/webhook",
        data={
            "enabled": True,
            "append-orcid": True,
            "apikey": "APIKEY123",
            "email-notifications-enabled": True,
            "notification-email": "*****@*****.**",
        },
    )
    server_name = client.application.config["SERVER_NAME"]
    mockput.assert_has_calls(
        [
            call(
                "https://api.sandbox.orcid.org/1001-0001-0001-0001/webhook/"
                f"https%3A%2F%2F{server_name}%2Fservices%2F21%2Fupdated",
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer ABC123",
                    "Content-Length": "0",
                },
            ),
            call(
                "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/"
                f"https%3A%2F%2F{server_name}%2Fservices%2F22%2Fupdated",
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer ABC123",
                    "Content-Length": "0",
                },
            ),
            call(
                "https://api.sandbox.orcid.org/0000-0000-0000-11X2/webhook/"
                f"https%3A%2F%2F{server_name}%2Fservices%2F30%2Fupdated",
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer ABC123",
                    "Content-Length": "0",
                },
            ),
        ]
    )

    # Link other org to the users
    org2 = Organisation.select().where(Organisation.id != org.id).first()
    UserOrg.insert_many([dict(user_id=u.id, org_id=org2.id) for u in org.users]).execute()
    org2.webhook_enabled = True
    org2.save()
    resp = client.delete("/api/v1/webhook")

    mockput.reset_mock()
    resp = client.put(
        "/api/v1/webhook",
        data={
            "enabled": False,
            "url": "https://CALL-BACK.edu/callback",
            "append-orcid": False,
            "email-notifications-enabled": True,
            "notification-email": "*****@*****.**",
        },
    )
    mockput.assert_not_called()

    # Test update summary:
    User.update(
        orcid_updated_at=datetime.date.today().replace(day=1) - datetime.timedelta(days=15)
    ).execute()
    send_email.reset_mock()
    utils.send_orcid_update_summary()
    send_email.assert_called_once()
    client.logout()
예제 #7
0
def test_orcid_login_callback_researcher_flow(client, mocker):
    """Test login from orcid callback function for researcher and display profile."""
    fetch_token = mocker.patch("orcid_hub.OAuth2Session.fetch_token",
                               side_effect=fetch_token_mock)
    mocker.patch(
        "orcid_hub.orcid_client.MemberAPI.create_or_update_affiliation",
        side_effect=affiliation_mock)
    org = Organisation.create(
        name="THE ORGANISATION:test_orcid_login_callback_researcher_flow",
        tuakiri_name=
        "THE ORGANISATION:test_orcid_login_callback_researcher_flow",
        confirmed=True,
        orcid_client_id="CLIENT ID",
        orcid_secret="Client Secret",
        city="CITY",
        country="COUNTRY",
        disambiguated_id="ID",
        disambiguation_source="RINGGOLD",
        is_email_sent=True)
    u = User.create(email="*****@*****.**",
                    name="TEST USER",
                    roles=Role.RESEARCHER,
                    orcid="123",
                    confirmed=True,
                    organisation=org)
    UserOrg.create(user=u, org=org, is_admin=False)
    token = utils.new_invitation_token()
    UserInvitation.create(email=u.email,
                          token=token,
                          affiliations=Affiliation.EMP,
                          org=org,
                          invitee=u)

    resp = client.get(f"/orcid/login/{token}")
    assert resp.status_code == 200
    assert token.encode() in resp.data

    state = session['oauth_state']

    resp = client.get(f"/auth/?state={state}&login=1")
    assert resp.status_code == 302
    assert resp.location.endswith("/link")
    fetch_token.assert_called_once()

    resp = client.get(f"/auth/?invitation_token={token}&login=1")
    assert resp.status_code == 302
    assert urlparse(resp.location).path == "/"
    fetch_token.assert_called_once()

    resp = client.get(f"/auth/?invitation_token={token}&login=1",
                      follow_redirects=True)
    assert b"Danger" in resp.data
    assert b"Something went wrong, Please retry giving permissions " in resp.data

    fetch_token.reset_mock()
    resp = client.get(f"/auth/?invitation_token={token}&state={state}",
                      follow_redirects=True)
    assert b"Warning" in resp.data
    assert b"The ORCID Hub was not able to automatically write an affiliation" in resp.data

    OrcidToken.delete().where(OrcidToken.user == u,
                              OrcidToken.org == org).execute()
    fetch_token.reset_mock()
    resp = client.get(f"/auth/?invitation_token={token}&state={state}&login=1")
    assert resp.status_code == 302
    assert resp.location.endswith("/profile")
    fetch_token.assert_called_once()
    assert OrcidToken.select().where(OrcidToken.user == u).count() == 1

    resp = client.get(f"/orcid/login/{token}", follow_redirects=True)
    assert b"You have already given permission" in resp.data
예제 #8
0
def test_webhook_registration(app_req_ctx):
    """Test webhook registration."""
    user = User.get(email="*****@*****.**")
    org = user.organisation
    orcid_id = "0000-0000-0000-00X3"
    client = Client.get(org=org)
    with app_req_ctx("/oauth/token",
                     method="POST",
                     data=dict(grant_type="client_credentials",
                               client_id=client.client_id,
                               client_secret=client.client_secret,
                               scope="/webhook")) as ctx:
        login_user(user)
        rv = ctx.app.full_dispatch_request()
        assert rv.status_code == 200
        data = json.loads(rv.data)
        token = Token.get(user=user, _scopes="/webhook")
        client = Client.get(client_id="CLIENT_ID")
        token = Token.get(client=client)
        assert data["access_token"] == token.access_token
        assert data["expires_in"] == ctx.app.config[
            "OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"]
        assert data["token_type"] == token.token_type
        # prevously created access token should be removed

    with app_req_ctx(
            f"/api/v1.0/{orcid_id}/webhook/http%3A%2F%2FCALL-BACK",
            method="PUT",
            headers=dict(authorization=f"Bearer {token.access_token}")
    ) as ctx, patch("orcid_hub.utils.requests.post") as mockpost, patch(
            "orcid_hub.utils.requests.put") as mockput:
        # Access toke request resp:
        mockresp = MagicMock(status_code=201)
        mockresp.json.return_value = {
            "access_token": "ACCESS-TOKEN-123",
            "token_type": "bearer",
            "refresh_token": "REFRESH-TOKEN-123",
            "expires_in": 99999,
            "scope": "/webhook",
            "orcid": None
        }
        mockpost.return_value = mockresp
        # Webhook registration response:
        mockresp = MagicMock(status_code=201, data=b'')
        # mockresp.raw.stream = lambda *args, **kwargs: iter([b"""{"data": "TEST"}"""])
        mockresp.raw.headers = {
            "Server": "TEST123",
            "Connection": "keep-alive",
            "Location": "LOCATION",
            "Pragma": "no-cache",
            "Expires": "0",
        }
        mockput.return_value = mockresp
        resp = ctx.app.full_dispatch_request()
        assert resp.status_code == 201
        args, kwargs = mockpost.call_args
        assert args[0] == "https://sandbox.orcid.org/oauth/token"
        assert kwargs["data"] == {
            "client_id": "APP-12345678",
            "client_secret": "CLIENT-SECRET",
            "scope": "/webhook",
            "grant_type": "client_credentials"
        }
        assert kwargs["headers"] == {"Accept": "application/json"}

        args, kwargs = mockput.call_args
        assert args[
            0] == "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/http%3A%2F%2FCALL-BACK"
        assert kwargs["headers"] == {
            "Accept": "application/json",
            "Authorization": "Bearer ACCESS-TOKEN-123",
            "Content-Length": "0"
        }

        q = OrcidToken.select().where(OrcidToken.org == org,
                                      OrcidToken.scope == "/webhook")
        assert q.count() == 1
        orcid_token = q.first()
        assert orcid_token.access_token == "ACCESS-TOKEN-123"
        assert orcid_token.refresh_token == "REFRESH-TOKEN-123"
        assert orcid_token.expires_in == 99999
        assert orcid_token.scope == "/webhook"

    with app_req_ctx(
            f"/api/v1.0/{orcid_id}/webhook/http%3A%2F%2FCALL-BACK",
            method="DELETE",
            headers=dict(authorization=f"Bearer {token.access_token}")
    ) as ctx, patch("orcid_hub.utils.requests.delete") as mockdelete:
        # Webhook deletion response:
        mockresp = MagicMock(status_code=204, data=b'')
        # mockresp.raw.stream = lambda *args, **kwargs: iter([b"""{"data": "TEST"}"""])
        mockresp.raw.headers = {
            "Server": "TEST123",
            "Connection": "keep-alive",
            "Location": "LOCATION",
            "Pragma": "no-cache",
            "Expires": "0",
        }
        mockdelete.return_value = mockresp
        resp = ctx.app.full_dispatch_request()
        assert resp.status_code == 204

        args, kwargs = mockput.call_args
        assert args[
            0] == "https://api.sandbox.orcid.org/0000-0000-0000-00X3/webhook/http%3A%2F%2FCALL-BACK"
        assert kwargs["headers"] == {
            "Accept": "application/json",
            "Authorization": "Bearer ACCESS-TOKEN-123",
            "Content-Length": "0"
        }

        q = OrcidToken.select().where(OrcidToken.org == org,
                                      OrcidToken.scope == "/webhook")
        assert q.count() == 1
        token = q.first()
        assert token.access_token == "ACCESS-TOKEN-123"
        assert token.refresh_token == "REFRESH-TOKEN-123"
        assert token.expires_in == 99999
        assert token.scope == "/webhook"