text:
# -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown which is licensed under the MIT.
#++
#

require 'minitest/autorun'
require 'kramdown'
require 'yaml'
require 'tmpdir'

begin
  require 'kramdown/converter/syntax_highlighter/rouge'

  Kramdown::Converter::SyntaxHighlighter::Rouge.formatter_class.send(:define_method, :format) do |tokens, &b|
    super(tokens, &b).sub(/<\/code><\/pre>\n?/, "
\n")
  end

  # custom formatter for tests
  class RougeHTMLFormatters < Kramdown::Converter::SyntaxHighlighter::Rouge.formatter_class

    tag 'rouge_html_formatters'

    def stream(tokens, &b)
      yield %(
)
      super
      yield %(
)
    end

  end
rescue LoadError, SyntaxError, NameError
end

Encoding.default_external = 'utf-8'

class TestFiles < Minitest::Test

  EXCLUDE_KD_FILES = [].compact

  # Generate test methods for kramdown-to-xxx conversion
  Dir[File.dirname(__FILE__) + '/testcases/**/*.text'].each do |text_file|
    next if EXCLUDE_KD_FILES.any? {|f| text_file =~ /#{f}/ }
    basename = text_file.sub(/\.text$/, '')
    opts_file = text_file.sub(/\.text$/, '.options')
    (Dir[basename + ".*"] - [text_file, opts_file]).each do |output_file|
      output_format = File.extname(output_file)[1..-1]
      next unless Kramdown::Converter.const_defined?(output_format[0..0].upcase + output_format[1..-1])
      define_method('test_' + text_file.tr('.', '_') + "_to_#{output_format}") do
        opts_file = File.join(File.dirname(text_file), 'options') unless File.exist?(opts_file)
        options = File.exist?(opts_file) ? YAML.load(File.read(opts_file)) : {auto_ids: false, footnote_nr: 1}
        doc = Kramdown::Document.new(File.read(text_file), options)
        assert_equal(File.read(output_file), doc.send("to_#{output_format}"))
      end
    end
  end

  # Generate test methods for html-to-{html,kramdown} conversion
  `tidy -v 2>&1`
  if $?.exitstatus != 0
    warn("Skipping html-to-{html,kramdown} tests because tidy executable is missing")
  else
    EXCLUDE_HTML_FILES = [
      'test/testcases/block/06_codeblock/whitespace.html', # bc of span inside pre
      'test/testcases/block/09_html/simple.html', # bc of xml elements
      'test/testcases/span/03_codespan/highlighting.html', # bc of span elements inside code element
      'test/testcases/block/04_header/with_auto_ids.html', # bc of auto_ids=true option
      'test/testcases/block/04_header/header_type_offset.html', # bc of header_offset option
      'test/testcases/block/06_codeblock/rouge/simple.html', # bc of double surrounding

      'test/testcases/block/06_codeblock/rouge/multiple.html', # bc of double surrounding

      'test/testcases/block/06_codeblock/highlighting.html', # bc of span elements inside code element
      'test/testcases/block/06_codeblock/highlighting-opts.html', # bc of span elements inside code element
      'test/testcases/block/06_codeblock/guess_lang_css_class.html', # bc of double surrounding

      'test/testcases/block/12_extension/options3.html', # bc of rouge
      'test/testcases/block/14_table/empty_tag_in_cell.html', # bc of tidy
      'test/testcases/block/15_math/mathjax_preview.html', # bc of mathjax preview
      'test/testcases/block/15_math/mathjax_preview_simple.html', # bc of mathjax preview
      'test/testcases/block/15_math/mathjax_preview_as_code.html', # bc of mathjax preview
      'test/testcases/span/05_html/mark_element.html', # bc of tidy
      'test/testcases/block/09_html/xml.html', # bc of tidy
      'test/testcases/span/05_html/xml.html', # bc of tidy
    ].compact
    EXCLUDE_HTML_TEXT_FILES = [
      'test/testcases/block/09_html/parse_as_span.htmlinput',
      'test/testcases/block/09_html/parse_as_raw.htmlinput',
    ].compact
    Dir[File.dirname(__FILE__) + '/testcases/**/*.{html,htmlinput}'].each do |html_file|
      next if EXCLUDE_HTML_FILES.any? {|f| html_file =~ /#{f}/ }

      out_files = []
      out_files << [(html_file =~ /\.htmlinput$/ ? html_file.sub(/input$/, '') : html_file), :to_html]
      if html_file =~ /\.htmlinput$/ && EXCLUDE_HTML_TEXT_FILES.none? {|f| html_file =~ /#{f}/ }
        out_files << [html_file.sub(/htmlinput$/, 'text'), :to_kramdown]
      end
      out_files.select {|f, _| File.exist?(f) }.each do |out_file, out_method|
        define_method('test_' + html_file.tr('.', '_') + "_to_#{File.extname(out_file)}") do
          opts_file = html_file.sub(/\.html(input)?$/, '.options')
          opts_file = File.join(File.dirname(html_file), 'options') unless File.exist?(opts_file)
          options = File.exist?(opts_file) ? YAML.load(File.read(opts_file)) : {auto_ids: false, footnote_nr: 1}
          doc = Kramdown::Document.new(File.read(html_file), options.merge(input: 'html'))
          if out_method == :to_html
            assert_equal(tidy_output(File.read(out_file)), tidy_output(doc.send(out_method)))
          else
            assert_equal(File.read(out_file), doc.send(out_method))
          end
        end
      end
    end
  end

  def tidy_output(out)
    cmd = "tidy -q --doctype omit #{RUBY_VERSION >= '1.9' ? '-utf8' : '-raw'} 2>/dev/null"
    result = IO.popen(cmd, 'r+') do |io|
      io.write(out)
      io.close_write
      io.read
    end
    if $?.exitstatus == 2
      raise "Problem using tidy"
    end
    result
  end

  # Generate test methods for text-to-latex conversion and compilation
  `latex -v 2>&1`
  if $?.exitstatus != 0
    warn("Skipping latex compilation tests because latex executable is missing")
  else
    EXCLUDE_LATEX_FILES = [
      'test/testcases/span/01_link/image_in_a.text', # bc of image link
      'test/testcases/span/01_link/imagelinks.text', # bc of image links
      'test/testcases/span/01_link/empty_title.text',
      'test/testcases/span/04_footnote/markers.text', # bc of footnote in header
      'test/testcases/block/06_codeblock/with_lang_in_fenced_block_name_with_dash.text',
      'test/testcases/block/06_codeblock/with_lang_in_fenced_block_any_char.text',
      'test/testcases/block/03_paragraph/standalone_image.text', # bc of standalone image
    ].compact
    Dir[File.dirname(__FILE__) + '/testcases/**/*.text'].each do |text_file|
      next if EXCLUDE_LATEX_FILES.any? {|f| text_file =~ /#{f}$/ }
      define_method('test_' + text_file.tr('.', '_') + "_to_latex_compilation") do
        latex = Kramdown::Document.new(File.read(text_file), auto_ids: false, footnote_nr: 1,
                                       template: 'document').to_latex
        Dir.mktmpdir do |tmpdir|
          result = IO.popen("latex -output-directory='#{tmpdir}' 2>/dev/null", 'r+') do |io|
            io.write(latex)
            io.close_write
            io.read
          end
          assert($?.exitstatus == 0, result.scan(/^!(.*\n.*)/).join("\n"))
        end
      end
    end
  end

  # Generate test methods for text->kramdown->html conversion
  `tidy -v 2>&1`
  if $?.exitstatus != 0
    warn("Skipping text->kramdown->html tests because tidy executable is missing")
  else
    EXCLUDE_TEXT_FILES = [
      'test/testcases/span/05_html/markdown_attr.text',  # bc of markdown attr
      'test/testcases/block/09_html/markdown_attr.text', # bc of markdown attr
      'test/testcases/span/extension/options.text',      # bc of parse_span_html option
      'test/testcases/block/12_extension/options.text',  # bc of options option
      'test/testcases/block/12_extension/options3.text', # bc of options option
      'test/testcases/block/09_html/content_model/tables.text',  # bc of parse_block_html option
      'test/testcases/block/09_html/html_to_native/header.text', # bc of auto_ids option that interferes
      'test/testcases/block/09_html/html_to_native/table_simple.text', # bc of tr style attr getting removed
      'test/testcases/block/09_html/simple.text',        # bc of webgen:block elements
      'test/testcases/block/11_ial/simple.text',         # bc of change of ordering of attributes in header
      'test/testcases/span/extension/comment.text',      # bc of comment text modifications (can this be avoided?)
      'test/testcases/block/04_header/header_type_offset.text', # bc of header_offset being applied twice
      'test/testcases/block/06_codeblock/rouge/simple.text',
      'test/testcases/block/06_codeblock/rouge/multiple.text', # check, what document contain more, than one code block
      'test/testcases/block/14_table/empty_tag_in_cell.text', # bc of tidy
      'test/testcases/span/01_link/link_defs_with_ial.text', # bc of attribute ordering
      'test/testcases/span/05_html/mark_element.text', # bc of tidy
      'test/testcases/block/09_html/xml.text', # bc of tidy
      'test/testcases/span/05_html/xml.text', # bc of tidy
      'test/testcases/block/03_paragraph/standalone_image.text', # bc of standalone image
    ].compact
    Dir[File.dirname(__FILE__) + '/testcases/**/*.text'].each do |text_file|
      next if EXCLUDE_TEXT_FILES.any? {|f| text_file =~ /#{f}$/ }
      html_file = text_file.sub(/\.text$/, '.html')
      next unless File.exist?(html_file)
      define_method('test_' + text_file.tr('.', '_') + "_to_kramdown_to_html") do
        opts_file = text_file.sub(/\.text$/, '.options')
        opts_file = File.join(File.dirname(text_file), 'options') unless File.exist?(opts_file)
        options = File.exist?(opts_file) ? YAML.load(File.read(opts_file)) : {auto_ids: false, footnote_nr: 1}
        kdtext = Kramdown::Document.new(File.read(text_file), options).to_kramdown
        html = Kramdown::Document.new(kdtext, options).to_html
        assert_equal(tidy_output(File.read(html_file)), tidy_output(html))
      end
    end
  end

  # Generate test methods for html-to-kramdown-to-html conversion
  `tidy -v 2>&1`
  if $?.exitstatus != 0
    warn("Skipping html-to-kramdown-to-html tests because tidy executable is missing")
  else
    EXCLUDE_HTML_KD_FILES = [
      'test/testcases/span/extension/options.html',        # bc of parse_span_html option
      'test/testcases/span/05_html/normal.html',           # bc of br tag before closing p tag
      'test/testcases/block/12_extension/nomarkdown.html', # bc of nomarkdown extension
      'test/testcases/block/12_extension/options3.html', # bc of rouge
      'test/testcases/block/09_html/simple.html',          # bc of webgen:block elements
      'test/testcases/block/09_html/markdown_attr.html',   # bc of markdown attr
      'test/testcases/block/09_html/html_to_native/table_simple.html', # bc of invalidly converted simple table
      'test/testcases/block/06_codeblock/whitespace.html', # bc of entity to char conversion
      'test/testcases/block/06_codeblock/rouge/simple.html', # bc of double surrounding

      'test/testcases/block/06_codeblock/rouge/multiple.html', # bc of double surrounding

      'test/testcases/block/06_codeblock/guess_lang_css_class.html', # bc of double surrounding

      'test/testcases/block/06_codeblock/highlighting.html', # bc of span elements inside code element
      'test/testcases/block/06_codeblock/highlighting-opts.html', # bc of span elements inside code element
      'test/testcases/block/11_ial/simple.html',           # bc of change of ordering of attributes in header
      'test/testcases/span/03_codespan/highlighting.html', # bc of span elements inside code element
      'test/testcases/block/04_header/with_auto_ids.html', # bc of auto_ids=true option
      'test/testcases/block/04_header/header_type_offset.html', # bc of header_offset option
      'test/testcases/block/16_toc/toc_exclude.html',      # bc of different attribute ordering
      'test/testcases/span/autolinks/url_links.html',      # bc of quot entity being converted to char
      'test/testcases/block/14_table/empty_tag_in_cell.html', # bc of tidy
      'test/testcases/block/15_math/mathjax_preview.html', # bc of mathjax preview
      'test/testcases/block/15_math/mathjax_preview_simple.html', # bc of mathjax preview
      'test/testcases/block/15_math/mathjax_preview_as_code.html', # bc of mathjax preview
      'test/testcases/span/01_link/link_defs_with_ial.html', # bc of attribute ordering
      'test/testcases/span/05_html/mark_element.html', # bc of tidy
      'test/testcases/block/09_html/xml.html', # bc of tidy
      'test/testcases/span/05_html/xml.html', # bc of tidy
      'test/testcases/block/03_paragraph/standalone_image.html', # bc of standalone image
    ].compact
    Dir[File.dirname(__FILE__) + '/testcases/**/*.html'].each do |html_file|
      next if EXCLUDE_HTML_KD_FILES.any? {|f| html_file =~ /#{f}$/ }
      define_method('test_' + html_file.tr('.', '_') + "_to_kramdown_to_html") do
        kd = Kramdown::Document.new(File.read(html_file), input: 'html',
                                    auto_ids: false, footnote_nr: 1).to_kramdown
        opts_file = html_file.sub(/\.html$/, '.options')
        opts_file = File.join(File.dirname(html_file), 'options') unless File.exist?(opts_file)
        options = File.exist?(opts_file) ? YAML.load(File.read(opts_file)) : {auto_ids: false, footnote_nr: 1}
        doc = Kramdown::Document.new(kd, options)
        assert_equal(tidy_output(File.read(html_file)), tidy_output(doc.to_html))
      end
    end
  end

  # Generate test methods for text-manpage conversion
  Dir[File.dirname(__FILE__) + '/testcases/man/**/*.text'].each do |text_file|
    define_method('test_' + text_file.tr('.', '_') + "_to_man") do
      man_file = text_file.sub(/\.text$/, '.man')
      doc = Kramdown::Document.new(File.read(text_file))
      assert_equal(File.read(man_file), doc.to_man)
    end
  end

  EXCLUDE_MODIFY = [
    'test/testcases/block/06_codeblock/rouge/multiple.text', # bc of HTMLFormater in options
  ].compact

  # Generate test methods for asserting that converters don't modify the document tree.
  Dir[File.dirname(__FILE__) + '/testcases/**/*.text'].each do |text_file|
    opts_file = text_file.sub(/\.text$/, '.options')
    options = File.exist?(opts_file) ? YAML.load(File.read(opts_file)) : {auto_ids: false, footnote_nr: 1}
    (Kramdown::Converter.constants.map(&:to_sym) -
     [:Base, :RemoveHtmlTags, :MathEngine, :SyntaxHighlighter]).each do |conv_class|
      next if EXCLUDE_MODIFY.any? {|f| text_file =~ /#{f}$/ }
      define_method("test_whether_#{conv_class}_modifies_tree_with_file_#{text_file.tr('.', '_')}") do
        doc = Kramdown::Document.new(File.read(text_file), options)
        options_before = Marshal.load(Marshal.dump(doc.options))
        tree_before = Marshal.load(Marshal.dump(doc.root))
        Kramdown::Converter.const_get(conv_class).convert(doc.root, doc.options)
        assert_equal(options_before, doc.options)
        assert_tree_not_changed(tree_before, doc.root)
      end
    end
  end

  def assert_tree_not_changed(old, new)
    assert_equal(old.type, new.type, "type mismatch")
    if old.value.kind_of?(Kramdown::Element)
      assert_tree_not_changed(old.value, new.value)
    else
      assert(old.value == new.value, "value mismatch")
    end
    assert_equal(old.attr, new.attr, "attr mismatch")
    assert_equal(old.options, new.options, "options mismatch")
    assert_equal(old.children.length, new.children.length, "children count mismatch")

    old.children.each_with_index do |child, index|
      assert_tree_not_changed(child, new.children[index])
    end
  end

end


text:
"""Tests for core functions."""

import copy
import json
import yaml
from datetime import datetime
from io import BytesIO
import os
from uuid import uuid4

from flask_login import current_user
import pytest

from orcid_hub.apis import yamlfy
from orcid_hub.data_apis import plural
from orcid_hub.models import (AffiliationRecord, AsyncOrcidResponse, Client, OrcidToken,
                              Organisation, Task, TaskType, Token, User, UserInvitation)
from unittest.mock import patch, MagicMock
from utils import get_profile as get_profile_data, get_resources as get_resources_data, readup_test_data

# Test data directory
data_path = os.path.join(os.path.dirname(__file__), "data")

def test_plural():
    """Test noun pluralization."""
    assert plural("wolf") == "wolves"
    assert plural("knife") == "knives"
    assert plural("potato") == "potatoes"
    assert plural("cactus") == "cacti"
    # assert plural("criterion") == "criteria"
    assert plural("organisation") == "organisations"
    assert plural("community") == "communities"
    assert plural("six") == "sixes"
    assert plural("carrot") == "carrots"

def test_get_oauth_access_token(client):
    """Test the acquisition of OAuth access token."""
    Token.update(expires=datetime(2017, 1, 1, 0, 0)).execute()
    resp = client.post(
            "/oauth/token",
            data=dict(
                grant_type="client_credentials",
                client_id="CLIENT_ID",
                client_secret="CLIENT_SECRET"))
    assert resp.status_code == 200
    c = Client.get(client_id="CLIENT_ID")
    token = Token.select().where(Token.client == c, Token.access_token != "TEST").first()
    assert resp.json["access_token"] == token.access_token
    assert resp.json["expires_in"] == client.application.config["OAUTH2_PROVIDER_TOKEN_EXPIRES_IN"]
    assert resp.json["token_type"] == token.token_type
    # prevously created access token should be removed
    assert not Token.select().where(Token.access_token == "TEST").exists()

    resp = client.post(
            "/oauth/token",
            data=dict(
                grant_type="client_credentials",
                client_id="NOT-EXISTING-CLIENT_ID",
                client_secret="CLIENT_SECRET"))
    # login_user(user, remember=True)
    assert resp.status_code == 401
    assert resp.json["error"] == "invalid_client"

    resp = client.post(
            "/oauth/token",
            data=dict(
                grant_type="client_credentials",
                client_id="CLIENT_ID",
                client_secret="INCORRECT"))
    # login_user(user, remember=True)
    assert resp.status_code == 401
    assert resp.json["error"] == "invalid_client"

    resp = client.post(
            "/oauth/token",
            data=dict(
                grant_type="INCORRECT",
                client_id="NOT-EXISTING-CLIENT_ID",
                client_secret="CLIENT_SECRET"))
    # login_user(user, remember=True)
    assert resp.status_code == 400
    assert resp.json["error"] == "unsupported_grant_type"

def test_multiple_access_token(client):
    """Test the acquisition of OAuth access token."""
    resp = client.post(
            "/oauth/token",
            data=dict(
                grant_type="client_credentials",
                client_id="CLIENT_ID",
                client_secret="CLIENT_SECRET"))
    assert resp.status_code == 200
    data = json.loads(resp.data)
    c = Client.get(client_id="CLIENT_ID")
    token = Token.get(client=c)
    assert data["access_token"] != token.access_token
    assert Token.select().count() == 2

    resp = client.post(
            "/oauth/token",
            data=dict(
                grant_type="client_credentials",
                client_id="CLIENT_ID",
                client_secret="CLIENT_SECRET"))
    assert resp.status_code == 200
    data = json.loads(resp.data)
    assert Token.select().count() == 3

    token = Token.get(access_token=data["access_token"])
    token.expires = datetime(555, 5, 5)
    token.save()

    resp = client.post(
            "/oauth/token",
            data=dict(
                grant_type="client_credentials",
                client_id="CLIENT_ID",
                client_secret="CLIENT_SECRET"))
    assert resp.status_code == 200
    assert Token.select().count() == 3
    assert not Token.select().where(Token.access_token == token.access_token).exists()

    # Multiple clients and multiple access tokens:
    for o in Organisation.select():
        u = o.users.first()
        if u:
            c = Client.create(org=o, user=u, client_id=u.name, client_secret=u.name)
            resp = client.post(
                    "/oauth/token",
                    data=dict(
                        grant_type="client_credentials",
                        client_id=c.client_id,
                        client_secret=c.client_secret))
            assert resp.status_code == 200
            assert "access_token" in resp.json
            t = Token.select().where(Token.access_token == resp.json["access_token"]).first()
            assert t is not None
            assert t.user == u
            assert t.client == c

def test_me(client):
    """Test the echo endpoint."""
    user = User.get(email="app123@test0.edu")
    token = Token.get(user=user)
    resp = client.get("/api/me", headers=dict(authorization=f"Bearer {token.access_token}"))
    assert resp.status_code == 200
    assert resp.json["email"] == user.email
    assert resp.json["name"] == user.name

    # Test invalid token:
    resp = client.get("/api/me", headers=dict(authorization="Bearer INVALID"))
    assert resp.status_code == 401

    # Test expired token:
    token.expires = datetime(1971, 1, 1)
    token.save()
    resp = client.get("/api/me", headers=dict(authorization=f"Bearer {token.access_token}"))
    assert resp.status_code == 401

@pytest.mark.parametrize("resource", [
    "users",
    "tokens",
])
@pytest.mark.parametrize("version", [
    "v1",
])
def test_user_and_token_api(client, resource, version):
    """Test the echo endpoint."""
    user = User.get(email="researcher@test0.edu")
    org2_user = User.get(email="researcher@org2.edu")
    resp = client.get(
            f"/api/{version}/{resource}/ABC123", headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 400
    assert "error" in resp.json
    assert "incorrect identifier" in resp.json["error"].lower()

    resp = client.get(
            f"/api/{version}/{resource}/0000-0000-0000-0000",
            headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 400
    assert "error" in resp.json
    assert "incorrect identifier" in resp.json["error"].lower()

    resp = client.get(
            f"/api/{version}/{resource}/abc123@some.org",
            headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 404
    assert "error" in resp.json
    assert "not found" in resp.json["error"].lower()

    resp = client.get(
            f"/api/{version}/{resource}/0000-0000-0000-0001",
            headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 404
    assert "error" in resp.json
    assert "not found" in resp.json["error"].lower()

    for identifier in [
            user.email,
            user.orcid,
    ]:
        resp = client.get(
                f"/api/{version}/{resource}/{identifier}",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        if resource == "users":
            assert resp.json["email"] == user.email
            assert resp.json["eppn"] == user.eppn
            assert resp.json["orcid"] == user.orcid
        else:
            data = resp.json[0]
            token = OrcidToken.get(user_id=user.id)
            assert data["access_token"] == token.access_token

    if resource == "users":  # test user listing
        resp = client.get(
                f"/api/{version}/{resource}",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        assert len(resp.json) == 11

        resp = client.get(
                f"/api/{version}/{resource}?page=INVALID&page_size=INVALID",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        assert len(resp.json) == 11

        resp = client.get(
                f"/api/{version}/{resource}?page=2&page_size=3",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        assert len(resp.json) == 3

        resp = client.get(
                f"/api/{version}/{resource}?page_size=3",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        assert len(resp.json) == 3

        resp = client.get(
                f"/api/{version}/{resource}?page=42",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        assert len(resp.json) == 0

        resp = client.get(
                f"/api/{version}/{resource}?from_date=ABCD",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 422

        resp = client.get(
                f"/api/{version}/{resource}?from_date=2018-01-01",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        assert len(resp.json) == 4

        resp = client.get(
                f"/api/{version}/{resource}?to_date=2018-01-01",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        assert len(resp.json) == 7

        resp = client.get(
                f"/api/{version}/{resource}?from_date=2017-12-20&to_date=2017-12-21",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 200
        assert len(resp.json) == 2

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"))
        assert resp.status_code == 415

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=b"thsi doesn't make much sense.")
        assert resp.status_code == 415

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=json.dumps({"incorrect": "ERROR"}),
                content_type="application/json")
        assert resp.status_code == 422

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"incorrect": "ERROR"}),
                content_type="application/x-yaml")
        assert resp.status_code == 422

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"email": "ERROR@ERROR"}),
                content_type="application/x-yaml")
        assert resp.status_code == 422

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"orcid": "ERROR@ERROR"}),
                content_type="application/x-yaml")
        assert resp.status_code == 422

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"orcid": "1535-2535-3535-00X3"}),
                content_type="application/x-yaml")
        assert resp.status_code == 201
        assert resp.json["orcid"] == "1535-2535-3535-00X3"

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"email": "and_now_something@different.ac.nz"}),
                content_type="application/x-yaml")
        assert resp.status_code == 201
        assert resp.json["email"] == "and_now_something@different.ac.nz"

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({
                    "email": "and_now_something_more@different.ac.nz",
                    "orcid": "1826-2826-3826-00X3"
                }),
                content_type="application/x-yaml")
        assert resp.status_code == 201
        assert resp.json["orcid"] == "1826-2826-3826-00X3"
        assert resp.json["email"] == "and_now_something_more@different.ac.nz"

        # org_user = User.select().where(User.organisation == org, User.orcid.is_null(False)).first()
        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"email": user.email, "orcid": "1906-2906-3906-00X3"}),
                content_type="application/x-yaml")
        assert resp.status_code == 200
        assert resp.json["orcid"] == "1906-2906-3906-00X3"

        resp = client.post(f"/api/{version}/users/",
                           headers=dict(authorization="Bearer TEST"),
                           data=yaml.dump({
                               "email": "a_different_email_address@org.ac.nz",
                               "orcid": "1906-2906-3906-00X3"
                           }),
                           content_type="application/x-yaml")
        assert resp.status_code == 200
        assert resp.json["email"] == "a_different_email_address@org.ac.nz"
        assert resp.json["orcid"] == "1906-2906-3906-00X3"
        assert User.get(orcid="1906-2906-3906-00X3").email == "a_different_email_address@org.ac.nz"

        resp = client.post(
                f"/api/{version}/users/",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"email": org2_user.email, "orcid": "1447-2447-3447-00X3"}),
                content_type="application/x-yaml")
        assert resp.status_code == 400

        resp = client.put(
                f"/api/{version}/users/1906-2906-3906-00X3",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"email": "a_new_different_email_address@org.ac.nz"}),
                content_type="application/x-yaml")
        assert resp.status_code == 200
        assert resp.json["email"] == "a_new_different_email_address@org.ac.nz"
        assert User.get(orcid="1906-2906-3906-00X3").email == "a_new_different_email_address@org.ac.nz"

        User.update(confirmed=True).where(User.orcid == "1906-2906-3906-00X3")
        resp = client.patch(
                f"/api/{version}/users/a_new_different_email_address@org.ac.nz",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"confirmed": False}),
                content_type="application/x-yaml")
        assert resp.status_code == 200
        assert resp.json["email"] == "a_new_different_email_address@org.ac.nz"
        assert not User.get(orcid="1906-2906-3906-00X3").confirmed

        resp = client.patch(
                f"/api/{version}/users/NON-EXISTING",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"confirmed": False}),
                content_type="application/x-yaml")
        assert resp.status_code == 400

        resp = client.patch(
                f"/api/{version}/users/non-existing@correct.email.com",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"confirmed": False}),
                content_type="application/x-yaml")
        assert resp.status_code == 404

        resp = client.patch(
                f"/api/{version}/users/0000-0001-9436-9121",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"confirmed": False}),
                content_type="application/x-yaml")
        assert resp.status_code == 404

        resp = client.patch(
                f"/api/{version}/users/0000-0001-9436-XXXX",
                headers=dict(authorization="Bearer TEST"),
                data=yaml.dump({"confirmed": False}),
                content_type="application/x-yaml")
        assert resp.status_code == 400

        resp = client.delete(
                f"/api/{version}/users/0000-0001-9436-XXXX",
                headers=dict(authorization="Bearer TEST"),
                content_type="application/x-yaml")
        assert resp.status_code == 400

        resp = client.delete(
                f"/api/{version}/users/0000-0001-9436-9121",
                headers=dict(authorization="Bearer TEST"),
                content_type="application/x-yaml")
        assert resp.status_code == 404

        resp = client.delete(
                f"/api/{version}/users/1906-2906-3906-00X3",
                headers=dict(authorization="Bearer TEST"),
                content_type="application/x-yaml")
        assert resp.status_code == 204

        resp = client.delete(
                f"/api/{version}/users/and_now_something@different.ac.nz",
                headers=dict(authorization="Bearer TEST"),
                content_type="application/x-yaml")
        assert resp.status_code == 204
        assert not User.select().where(User.email == "and_now_something@different.ac.nz").exists()

    if resource == "tokens":
        user2 = User.get(email="researcher2@test0.edu")
        for identifier in [
                user2.email,
                user2.orcid,
        ]:
            resp = client.get(
                    f"/api/{version}/tokens/{identifier}",
                    headers=dict(authorization="Bearer TEST"))
            assert resp.status_code == 200
            assert resp.json == []

        OrcidToken.delete().where(OrcidToken.user == user).execute()
        for identifier in [
                user2.email,
                user2.orcid,
        ]:
            resp = client.post(f"/api/{version}/tokens/{identifier}",
                               data=json.dumps({
                                   "access_token": "ERROR",
                                   "expires_in": 631138518,
                                   "issue_time": "2018-09-11T00:31:50.534013",
                                   "refresh_token": "5f9d7d7a-b8ff-4744-ab9d-4006d2a2383c",
                                   "scopes": "/read-limited,/activities/update",
                               }),
                               content_type="application/json",
                               headers=dict(authorization="Bearer TEST"))
            assert resp.status_code == 400
            assert "error" in resp.json
            assert not OrcidToken.select().where(OrcidToken.user == user).exists()

            resp = client.post(f"/api/{version}/tokens/{identifier}",
                               data=json.dumps({
                                   "access_token": "4614050b-3a27-4793-a964-f3f3712e18b2",
                                   "expires_in": "ERROR",
                                   "issue_time": "2018-09-11T00:31:50.534013",
                                   "refresh_token": "5f9d7d7a-b8ff-4744-ab9d-4006d2a2383c",
                                   "scopes": "/read-limited,/activities/update",
                               }),
                               content_type="application/json",
                               headers=dict(authorization="Bearer TEST"))
            assert resp.status_code == 400
            assert "error" in resp.json
            assert not OrcidToken.select().where(OrcidToken.user == user).exists()

            resp = client.post(f"/api/{version}/tokens/{identifier}",
                               data=json.dumps({
                                   "access_token": "4614050b-3a27-4793-a964-f3f3712e18b2",
                                   "expires_in": 9999999,
                                   "issue_time": "ERROR",
                                   "refresh_token": "5f9d7d7a-b8ff-4744-ab9d-4006d2a2383c",
                                   "scopes": "/read-limited,/activities/update",
                               }),
                               content_type="application/json",
                               headers=dict(authorization="Bearer TEST"))
            assert resp.status_code == 400
            assert "error" in resp.json
            assert not OrcidToken.select().where(OrcidToken.user == user).exists()

            resp = client.post(f"/api/{version}/tokens/{identifier}",
                               data=json.dumps({
                                   "access_token": "4614050b-3a27-4793-a964-f3f3712e18b2",
                                   "expires_in": 9999999,
                                   "issue_time": "2018-09-11T00:31:50.534013",
                                   "refresh_token": "ERROR",
                                   "scopes": "/read-limited,/activities/update",
                               }),
                               content_type="application/json",
                               headers=dict(authorization="Bearer TEST"))
            assert resp.status_code == 400
            assert "error" in resp.json
            assert not OrcidToken.select().where(OrcidToken.user == user).exists()

            resp = client.post(f"/api/{version}/tokens/{identifier}",
                               data=json.dumps({
                                   "access_token": "4614050b-3a27-4793-a964-f3f3712e18b2",
                                   "expires_in": 9999999,
                                   "issue_time": "2018-09-11T00:31:50.534013",
                                   "refresh_token": "5f9d7d7a-b8ff-4744-ab9d-4006d2a2383c",
                                   "scopes": "ERROR",
                               }),
                               content_type="application/json",
                               headers=dict(authorization="Bearer TEST"))
            assert resp.status_code == 400
            assert "error" in resp.json
            assert not OrcidToken.select().where(OrcidToken.user == user).exists()

            resp = client.post(f"/api/{version}/tokens/{identifier}",
                               data=json.dumps({
                                   "access_token": "4614050b-3a27-4793-a964-f3f3712e18b2",
                                   "expires_in": 9999999,
                                   "issue_time": "2018-09-11T00:31:50.534013",
                                   "refresh_token": "5f9d7d7a-b8ff-4744-ab9d-4006d2a2383c",
                                   "scopes": "/read-limited,/activities/update",
                               }),
                               content_type="application/json",
                               headers=dict(authorization="Bearer TEST"))

            assert resp.status_code == 201
            assert OrcidToken.select().where(OrcidToken.user == user2).count() == 1

            OrcidToken.delete().where(OrcidToken.user == user2).execute()

    resp = client.get(f"/api/{version}/{resource}/{org2_user.email}",
                      headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 404
    assert "error" in resp.json

@pytest.mark.parametrize("url", [
    "/spec",
    "/spec.json",
    "/spec.yml",
])
def test_spec(client, url):
    """Test API specs."""
    resp = client.get(url)
    assert resp.status_code == 200

def test_spec_content(client):
    """Test API specs."""
    resp = client.get("/spec")
    assert resp.status_code == 200
    spec = resp.json
    for m in ["AffiliationTask", "AffiliationTaskRecord", "Error", "FundTask"]:
        assert m in spec["definitions"]

def test_yaml_spec(client):
    """Test API specs (the default entry point with yaml content type)."""
    resp = client.get("/spec", headers={"Accept": "text/yaml"})
    assert resp.status_code == 200

def test_yamlfy(app):
    """Test yamlfy function."""
    from flask import Response
    assert isinstance(yamlfy(1), Response)
    assert isinstance(yamlfy(1, 2, 3), Response)
    assert isinstance(yamlfy(key_arg=42), Response)
    with pytest.raises(TypeError):
        yamlfy(1, 2, 3, key_arg=42)
    # with app.app_context():
    resp = yamlfy(datetime(2018, 7, 10, 16, 26, 25, 86519))
    assert yaml.load(resp.data) == datetime(2018, 7, 10, 16, 26, 25)

def test_api_docs(client):
    """Test API docs."""
    data = client.data
    tech_contact = data["tech_contact"]
    super_user = data["super_user"]
    client.login(tech_contact)
    # assert resp is None
    resp = client.get("/api-docs")
    assert resp.status_code == 200
    assert b"/spec" in resp.data
    assert b"ORCID HUB Data API" not in resp.data
    client.logout()

    client.login(super_user)
    resp = client.get("/api-docs")
    assert resp.status_code == 200
    assert b"/spec" in resp.data
    assert b"ORCID HUB Data API" in resp.data

def test_db_api(client):
    """Test DB API."""
    resp = client.get("/data/api/v0.1/organisations/", headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 200
    assert "objects" in resp.json
    assert len(resp.json["objects"]) == 4

    resp = client.get("/data/api/v0.1/tasks/", headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 200
    assert "objects" in resp.json
    assert len(resp.json["objects"]) == 0

    org = Organisation.select().where(Organisation.tuakiri_name.is_null(False)).first()
    resp = client.get(
            f"/data/api/v0.1/organisations/{org.id}",
            headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 200
    assert resp.json["name"] == org.name
    assert resp.json["tuakiri_name"] == org.tuakiri_name

    org = Organisation.select().where(Organisation.tuakiri_name.is_null(False), Organisation.id != org.id).first()
    resp = client.get(
            f"/data/api/v0.1/organisations/{org.id}",
            headers=dict(authorization="Bearer TEST"))
    assert resp.status_code == 200
    assert resp.json["name"] == org.name
    assert resp.json["tuakiri_name"] == org.tuakiri_name

def test_users_api(client):
    """Test user API."""
    c = Client.get(client_id="TEST0-ID")
    resp = client.post(
        "/oauth/token",
        content_type="application/x-www-form-urlencoded",
        data=f"grant_type=client_credentials&client_id={c.client_id}&client_secret={c.client_secret}")
    data = json.loads(resp.data)
    access_token = data["access_token"]
    resp = client.get(
        "/api/v1/users?page=1&page_size=2000",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json")

    data = json.loads(resp.data)
    assert list(data[0].keys()) == ["confirmed", "email", "eppn", "name", "orcid", "updated-at"]
    assert not any(u["email"] == "researcher102@test1.edu" for u in data)

    resp = client.get(
        "/api/v1/users?page=1&page_size=2000&from_date=2000-12-01&to_date=1999-01-01",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json")
    data = json.loads(resp.data)
    assert len(data) == 0

    resp = client.get(
        "/api/v1/users/researcher102@test1.edu",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json")
    assert resp.status_code == 404
    data = json.loads(resp.data)

    resp = client.get(
        "/api/v1/users/researcher102@test0.edu",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json")
    assert resp.status_code == 200
    data = json.loads(resp.data)
    assert data["email"] == "researcher102@test0.edu"

def test_affiliation_api(client, mocker):
    """Test affiliation API in various formats."""
    exception = mocker.patch.object(client.application.logger, "exception")
    capture_event = mocker.patch("sentry_sdk.transport.HttpTransport.capture_event")

    resp = client.post(
        "/oauth/token",
        content_type="application/x-www-form-urlencoded",
        data=b"grant_type=client_credentials&client_id=TEST0-ID&client_secret=TEST0-SECRET")
    data = json.loads(resp.data)
    access_token = data["access_token"]
    resp = client.post(
        "/api/v1/affiliations/?filename=TEST42.csv",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="text/csv",
        data=b"First Name,Last Name,email,Organisation,Affiliation Type,Role,Department,Start Date,"
        b"End Date,City,State,Country,Disambiguated Id,Disambiguated Source\n"
        b"Researcher,Par,researcher.020@mailinator.com,Royal Org1,Staff,Programme Guide - "
        b"ORCID,Research Funding,2016-09,,Wellington,SATE,NZ,,\n"
        b"Roshan,Pawar,researcher.010@mailinator.com,Royal Org1,Staff,AAA,Research "
        b"Funding,2016-09,,Wellington,SATE,NZ,,\n"
        b"Roshan,Pawar,researcher.010@mailinator.com,Royal Org1,Student,BBB,Research "
        b"Funding,2016-09,,Wellington,SATE,New Zealand,,")
    data = json.loads(resp.data)
    assert data["filename"] == "TEST42.csv"
    assert data["task-type"] == "AFFILIATION"
    assert len(data["records"]) == 3
    task_id = data["id"]

    resp = client.get("/api/v1/tasks", headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert tasks[0]["id"] == task_id

    resp = client.get(
        "/api/v1/tasks?type=AFFILIATION", headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert tasks[0]["id"] == task_id

    resp = client.get(
        "/api/v1/tasks?type=AFFILIATION&status=ACTIVE", headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert len(tasks) == 0

    resp = client.get(
        "/api/v1/tasks?type=AFFILIATION&status=RESET", headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert len(tasks) == 0

    resp = client.get(
        "/api/v1/tasks?type=AFFILIATION&status=INACTIVE", headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert len(tasks) == 1

    resp = client.get(
        "/api/v1/tasks?type=AFFILIATION&page=1&page_size=20",
        headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert tasks[0]["id"] == task_id

    task_copy = copy.deepcopy(data)
    del(task_copy["id"])
    task_copy["filename"] = "TASK-COPY.csv"
    resp = client.post(
        "/api/v1/affiliations/",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(task_copy))
    assert Task.select().count() == 2

    for r in data["records"]:
        del(r["id"])
        r["city"] = "TEST000"
    resp = client.post(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(data))
    data = json.loads(resp.data)
    assert len(resp.json["records"]) == 3
    # should get a new set of records

    del(data["records"][2])
    resp = client.post(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(data))
    assert len(resp.json["records"]) == 2

    incorrect_data = copy.deepcopy(data)
    incorrect_data["records"].insert(0, {
        "first-name": "TEST000 FN",
        "last-name": "TEST000 LN",
    })
    resp = client.post(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(incorrect_data))
    assert resp.status_code == 422
    assert resp.json["error"] == "Validation error."

    resp = client.get(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    incorrect_data = copy.deepcopy(resp.json)
    incorrect_data["records"].insert(0, {
        "email": "test1234@test.edu",
        "first-name": "TEST000 FN",
        "last-name": "TEST000 LN",
    })
    resp = client.post(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(incorrect_data))
    assert resp.status_code == 422
    assert resp.json["error"] == "Validation error."

    resp = client.get(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    new_data = copy.deepcopy(resp.json)
    new_data["records"].insert(0, {
        "email": "test1234@test.edu",
        "first-name": "TEST000 FN",
        "last-name": "TEST000 LN",
        "affiliation-type": "staff",
        "city": "TEST000"
    })
    resp = client.post(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(new_data))
    data = json.loads(resp.data)
    assert resp.status_code == 200
    assert len(data["records"]) == 3

    resp = client.put(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(data))
    data = json.loads(resp.data)
    assert resp.status_code == 200
    assert len(resp.json["records"]) == 3

    resp = client.get(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    new_data = copy.deepcopy(resp.json)
    for i, r in enumerate(new_data["records"]):
        new_data["records"][i] = {"id": r["id"], "is-active": True}
    resp = client.patch(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(new_data))
    assert resp.status_code == 200
    assert len(resp.json["records"]) == 3
    assert all(r["is-active"] for r in resp.json["records"])
    assert all(r["city"] == "TEST000" for r in resp.json["records"])

    resp = client.head(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert "Last-Modified" in resp.headers

    resp = client.head(
        "/api/v1/affiliations/999999999",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 404

    resp = client.get(
        "/api/v1/affiliations/999999999",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 404

    resp = client.patch(
        "/api/v1/affiliations/999999999",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(new_data))
    assert resp.status_code == 404

    with patch.object(Task, "get", side_effect=Exception("ERROR")):
        resp = client.delete(
            f"/api/v1/affiliations/{task_id}",
            headers=dict(authorization=f"Bearer {access_token}"))
        assert resp.status_code == 400
        assert resp.json == {"error": "Unhandled exception occurred.", "exception": "ERROR"}

    resp = client.delete(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert Task.select().count() == 1

    resp = client.delete(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 404

    other_user = User.get(email="admin@test1.edu")
    other_task = Task.create(
        created_by=other_user,
        org=other_user.organisation,
        filename="OTHER.csv",
        task_type=TaskType.AFFILIATION)

    resp = client.head(
        f"/api/v1/affiliations/{other_task.id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 403

    resp = client.get(
        f"/api/v1/affiliations/{other_task.id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 403

    resp = client.patch(
        f"/api/v1/affiliations/{other_task.id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(new_data))
    assert resp.status_code == 403

    resp = client.delete(
        f"/api/v1/affiliations/{other_task.id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 403

    resp = client.patch(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=b'')
    assert resp.status_code == 400

    resp = client.post(
        "/api/v1/affiliations/?filename=TEST42.csv",
        headers=dict(authorization=f"Bearer {access_token}", accept="text/yaml"),
        content_type="text/yaml",
        data="""task-type: AFFILIATION
filename: TEST42.yml
records:
- affiliation-type: student
  city: Wellington
  country: NZ
  department: Research Funding
  email: researcher.010@mailinator.com
  first-name: Roshan
  last-name: Pawar
  organisation: Royal Org1
  role: BBB
  start-date: 2016-09
- affiliation-type: staff
  city: Wellington
  country: NZ
  department: Research Funding
  email: researcher.010@mailinator.com
  first-name: Roshan
  last-name: Pawar
  organisation: Royal Org1
  role: AAA
  start-date: 2016-09
- affiliation-type: staff
  city: Wellington
  country: NZ
  department: Research Funding
  email: researcher.020@mailinator.com
  first-name: Researcher
  is-active: false
  last-name: Par
  organisation: Royal Org1
  role: Programme Guide - ORCID
  start-date: 2016-09
""")
    assert resp.json["filename"] == "TEST42.yml"
    assert resp.json["task-type"] == "AFFILIATION"
    assert len(resp.json["records"]) == 3
    task_id = resp.json["id"]
    task = Task.get(id=task_id)
    assert task.affiliation_records.count() == 3

    resp = client.patch(
        f"/api/v1/tasks/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}", accept="text/yaml"),
        content_type="text/yaml",
        data="status: ACTIVE\n")
    assert Task.get(task_id).status == "ACTIVE"
    assert task.records.where(task.record_model.is_active).count() == 3

    resp = client.get(
        "/api/v1/tasks?type=AFFILIATION&status=ACTIVE", headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert len(tasks) == 1

    resp = client.put(
        f"/api/v1/tasks/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}", accept="application/json"),
        content_type="application/json",
        data="""{"status": "RESET"}""")
    assert Task.get(task_id).status == "RESET"

    resp = client.get(
        "/api/v1/tasks?type=AFFILIATION&status=ACTIVE", headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert len(tasks) == 0

    resp = client.get(
        "/api/v1/tasks?type=AFFILIATION&status=RESET", headers=dict(authorization=f"Bearer {access_token}"))
    tasks = json.loads(resp.data)
    assert len(tasks) == 1

    resp = client.put(
        f"/api/v1/tasks/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}", accept="application/json"),
        content_type="application/json",
        data="""{"status": "INVALID"}""")
    assert Task.get(task_id).status == "RESET"

    with patch("orcid_hub.models.Task.save", side_effect=Exception("FAILURE")):
        resp = client.put(
            f"/api/v1/tasks/{task_id}",
            headers=dict(authorization=f"Bearer {access_token}", accept="application/json"),
            content_type="application/json",
            data="""{"status": "ACTIVE"}""")
        assert Task.get(task_id).status == "RESET"
        assert resp.status_code == 400
        assert resp.json["exception"] == "FAILURE"

    resp = client.post(
        "/api/v1/tasks/999999999999",
        headers=dict(authorization=f"Bearer {access_token}", accept="application/json"),
        content_type="application/json",
        data="""{"status": "RESET"}""")
    assert resp.status_code == 404

    resp = client.get(
        f"/api/v1/tasks/{other_task.id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 403

    resp = client.put(
        f"/api/v1/affiliations/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}", accept="text/yaml"),
        content_type="text/yaml",
        data="""task-type: AFFILIATION
filename: TEST42.yml
records:
- affiliation-type: student
  id: 9999999999
  city: Wellington
  country: NZ
  department: Research Funding
  email: researcher.010@mailinator.com
  first-name: Roshan
  last-name: Pawar
  organisation: Royal Org1
  role: BBB
  start-date: 2016-09
""")
    assert resp.status_code == 400
    assert resp.json["error"] == "Unhandled exception occurred."
    assert "instance matching query does not exist" in resp.json["exception"]

    with patch.object(AffiliationRecord, "get", side_effect=Exception("ERROR")):
        resp = client.put(
            f"/api/v1/affiliations/{task_id}",
            headers=dict(authorization=f"Bearer {access_token}", accept="text/yaml"),
            content_type="text/yaml",
            data="""task-type: AFFILIATION
filename: TEST42.yml
records:
- affiliation-type: student
  id: 9999999999
  city: Wellington
  country: NZ
  department: Research Funding
  email: researcher.010@mailinator.com
  first-name: Roshan
  last-name: Pawar
  organisation: Royal Org1
  role: BBB
  start-date: 2016-09
""")
        assert resp.status_code == 400
        assert resp.json == {"error": "Unhandled exception occurred.", "exception": "ERROR"}

    resp = client.post(
        "/api/v1/affiliations/?filename=TEST42.csv",
        headers=dict(authorization=f"Bearer {access_token}", accept="text/yaml"),
        content_type="text/yaml",
        data="""task-type: INCORRECT
filename: TEST42.yml
records:
- affiliation-type: student
  id: 9999999999
""")
    assert resp.status_code == 422
    assert resp.json["error"] == "Validation error."
    assert "INCORRECT" in resp.json["message"]

    resp = client.post(
        "/api/v1/affiliations/?filename=TEST_ERROR.csv",
        headers=dict(authorization=f"Bearer {access_token}", accept="text/yaml"),
        content_type="text/yaml",
        data="""task-type: AFFILIATION
filename: TEST_ERROR.yml
records:
- affiliation-type: student
something fishy is going here...
""")
    assert resp.status_code == 415
    assert resp.json["error"] == "Invalid request format. Only JSON, CSV, or TSV are acceptable."
    assert "something fishy is going here..." in resp.json["message"]
    exception.assert_called()
    capture_event.assert_called()

    # attempt to use ID:0
    resp = client.put(
        "/api/v1/tasks/0",
        headers=dict(authorization=f"Bearer {access_token}", accept="application/json"),
        content_type="application/json",
        data="""{"status": "ACTIVE"}""")

    # Test if a user from the same organisation can access the task submitted by another user
    resp = client.post(
        "/api/v1/affiliations/?filename=empty-task.json",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data="""{
            "filename": "empty-task.json",
            "task-type": "AFFILIATION",
            "records": []
        }""")
    task_id = resp.json["id"]

    # Change the technical contact
    user = current_user
    org = Client.get(client_id="TEST0-ID").org
    user2 = org.users.where(User.id != org.tech_contact.id).first()
    org.tech_contact = user2
    org.save()

    resp = client.post(
        "/oauth/token",
        content_type="application/x-www-form-urlencoded",
        data=b"grant_type=client_credentials&client_id=TEST0-ID&client_secret=TEST0-SECRET")
    data = json.loads(resp.data)

    resp = client.get(
        f"/api/v1/tasks/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}", accept="application/json"))
    assert resp.status_code == 200
    assert resp.json["filename"] == "empty-task.json"
    assert resp.json["id"] == task_id
    # and the user stays the same
    assert user.id == current_user.id

def test_funding_api(client):
    """Test funding API in various formats."""
    admin = client.data.get("admin")
    resp = client.login(admin, follow_redirects=True)
    resp = client.post(
        "/load/researcher/funding",
        data={
            "file_": (
                BytesIO(
                    """Put Code,Title,Translated Title,Translated Title Language Code,Type,Organization Defined Type,Short Description,Amount,Currency,Start Date,End Date,Org Name,City,Region,Country,Disambiguated Org Identifier,Disambiguation Source,Visibility,ORCID iD,Email,First Name,Last Name,Name,Role,External Id Type,External Id Value,External Id Url,External Id Relationship,Identifier
,This is the project title,,,CONTRACT,Fast-Start,This is the project abstract,300000,NZD,2018,2021,Marsden Fund,Wellington,,NZ,http://dx.doi.org/10.13039/501100009193,FUNDREF,,0000-0002-9207-4933,,,,Associate Professor A Contributor 1,lead,grant_number,XXX1701,,SELF,
,This is the project title,,,CONTRACT,Fast-Start,This is the project abstract,300000,NZD,2018,2021,Marsden Fund,Wellington,,NZ,http://dx.doi.org/10.13039/501100009193,FUNDREF,,,,,,Dr B Contributor 2,co_lead,grant_number,XXX1701,,SELF,
,This is the project title,,,CONTRACT,Fast-Start,This is the project abstract,300000,NZD,2018,2021,Marsden Fund,Wellington,,NZ,http://dx.doi.org/10.13039/501100009193,FUNDREF,,,,,,Dr E Contributor 3,,grant_number,XXX1701,,SELF,
,This is the project title,,,CONTRACT,Fast-Start,This is the project abstract,300000,NZD,2018,2021,Marsden Fund,Wellington,,NZ,http://dx.doi.org/10.13039/501100009193,FUNDREF,,,contributor@test.eud,FN,LN,Dr E Contributor 4,,grant_number,XXX1701,,SELF,
,This is another project title,,,CONTRACT,Standard,This is another project abstract,800000,NZD,2018,2021,Marsden Fund,Wellington,,NZ,http://dx.doi.org/10.13039/501100009193,FUNDREF,,,contributor4@mailinator.com,,,Associate Professor F Contributor 4,lead,grant_number,XXX1702,,SELF,9999
,This is another project title,,,CONTRACT,Standard,This is another project abstract,800000,NZD,2018,2021,Marsden Fund,Wellington,,NZ,http://dx.doi.org/10.13039/501100009193,FUNDREF,,,contributor5@mailinator.com,John,Doe,,co_lead,grant_number,XXX1702,,SELF,8888 """.encode()  # noqa: E501
                ),  # noqa: E501
                "fundings042.csv",
            ),
        },
        follow_redirects=True)
    assert resp.status_code == 200
    assert Task.select().count() == 1

    # TODO: factor it out
    resp = client.post(
        "/oauth/token",
        content_type="application/x-www-form-urlencoded",
        data=b"grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET")
    data = json.loads(resp.data)
    access_token = data["access_token"]

    resp = client.get(
        "/api/v1/tasks?type=FUNDING",
        headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    task_id = int(data[0]["id"])

    resp = client.get(
        f"/api/v1/funds/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    assert len(data["records"]) == 2
    assert data["filename"] == "fundings042.csv"

    resp = client.post(
        "/api/v1/funds/?filename=fundings333.json",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(data))
    assert resp.status_code == 200
    assert Task.select().count() == 2

    records = data["records"]
    resp = client.post(
        "/api/v1/funds/?filename=fundings444.json",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 3

    resp = client.post(
        f"/api/v1/funds/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 3

    resp = client.head(
        f"/api/v1/funds/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert "Last-Modified" in resp.headers
    assert resp.status_code == 200

    resp = client.delete(
        f"/api/v1/funds/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 200
    assert Task.select().count() == 2

    resp = client.delete(
        f"/api/v1/tesks/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 404

    resp = client.head(
        f"/api/v1/funds/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 404

def test_work_api(client):
    """Test work API in various formats."""
    admin = client.data.get("admin")
    resp = client.login(admin, follow_redirects=True)
    resp = client.post(
        "/load/researcher/work",
        data={
            "file_": (open(os.path.join(os.path.dirname(__file__), "data", "example_works.json"), "rb"),
                      "works042.json"),
        },
        follow_redirects=True)
    assert resp.status_code == 200
    assert Task.select().count() == 1

    access_token = client.get_access_token()

    resp = client.get(
        "/api/v1/tasks?type=work",
        headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    task_id = int(data[0]["id"])

    resp = client.get(
        f"/api/v1/works/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    assert len(data["records"]) == 1
    assert data["filename"] == "works042.json"

    del(data["id"])
    resp = client.post(
        "/api/v1/works/?filename=works333.json",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(data))
    assert resp.status_code == 200
    assert Task.select().count() == 2

    records = data["records"]
    resp = client.post(
        "/api/v1/works/?filename=works444.json",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 3

    resp = client.post(
        f"/api/v1/works/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 3

    resp = client.head(
        f"/api/v1/works/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert "Last-Modified" in resp.headers
    assert resp.status_code == 200

    resp = client.delete(
        f"/api/v1/works/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 200
    assert Task.select().count() == 2

    resp = client.head(
        f"/api/v1/works/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 404

def test_peer_review_api(client):
    """Test peer review API in various formats."""
    admin = client.data.get("admin")
    resp = client.login(admin, follow_redirects=True)

    test_data_dir = os.path.join(os.path.dirname(__file__), "data")
    resp = client.post(
        "/load/researcher/peer_review",
        data={
            "file_": (open(
                os.path.join(test_data_dir, "test1_required_only.csv"),
                "rb"), "test1_required_only.csv"),
        },
        follow_redirects=True)
    # breakpoint()
    assert resp.status_code == 200
    assert Task.select().count() == 1
    Task.delete().execute()

    resp = client.post(
        "/load/researcher/peer_review",
        data={
            "file_": (open(
                os.path.join(test_data_dir, "example_peer_reviews.json"),
                "rb"), "peer-reviews042.json"),
        },
        follow_redirects=True)
    assert resp.status_code == 200
    assert Task.select().count() == 1

    access_token = client.get_access_token()

    resp = client.get(
        "/api/v1/tasks?type=PEER_REVIEW",
        headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    task_id = int(data[0]["id"])

    resp = client.get(
        f"/api/v1/peer-reviews/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    assert len(data["records"]) == 1
    assert data["filename"] == "peer-reviews042.json"

    del(data["id"])
    resp = client.post(
        "/api/v1/peer-reviews/?filename=peer_reviews333.json",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(data))
    assert resp.status_code == 200
    assert Task.select().count() == 2

    records = data["records"]
    resp = client.post(
        "/api/v1/peer-reviews/?filename=peer_reviews444.json",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 3

    resp = client.post(
        "/api/v1/peer-reviews",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 4

    resp = client.post(
        f"/api/v1/peer-reviews/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"),
        content_type="application/json",
        data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 4

    resp = client.head(
        f"/api/v1/peer-reviews/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert "Last-Modified" in resp.headers
    assert resp.status_code == 200

    resp = client.delete(
        f"/api/v1/peer-reviews/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 200
    assert Task.select().count() == 3

    resp = client.head(
        f"/api/v1/peer-reviews/{task_id}",
        headers=dict(authorization=f"Bearer {access_token}"))
    assert resp.status_code == 404

def test_proxy_get_profile(client):
    """Test the echo endpoint."""
    user = User.get(email="app123@test0.edu")
    token = Token.get(user=user)
    orcid_id = "0000-0000-0000-00X3"

    with patch("orcid_hub.apis.requests.Session.send") as mocksend:
        headers = {
            "Server": "TEST123",
            "Content-Type": "application/json;charset=UTF-8",
            "Transfer-Encoding": "chunked",
            "Connection": "keep-alive",
            "Access-Control-Allow-Origin": "*",
            "Cache-Control": "no -cache, no-store, max-age=0, must-revalidate",
            "Pragma": "no-cache",
            "Expires": "0",
        }
        mockresp = MagicMock(status_code=200,
                             headers=headers,
                             json={"data": "TEST"},
                             text='{"data": "TEST"}')
        mockresp.raw.stream = lambda *args, **kwargs: iter([b"""{"data": "TEST"}"""])
        mockresp.raw.headers = headers
        mocksend.return_value = mockresp
        resp = client.get(
            f"/orcid/api/v2.23/{orcid_id}",
            headers=dict(authorization=f"Bearer {token.access_token}"))
        assert resp.status_code == 200
        args, kwargs = mocksend.call_args
        assert kwargs["stream"]
        assert args[0].url == f"https://api.sandbox.orcid.org/v2.23/{orcid_id}"
        assert args[0].headers["Authorization"] == "Bearer ORCID-TEST-ACCESS-TOKEN"
        assert resp.json == {"data": "TEST"}

        # Asynchronous call:
        resp = client.get(
            f"/orcid/api/v2.23/{orcid_id}?async=true",
            headers=dict(authorization=f"Bearer {token.access_token}"))
        assert resp.status_code == 202
        args, kwargs = mocksend.call_args
        assert args[0].url == f"https://api.sandbox.orcid.org/v2.23/{orcid_id}"
        assert args[0].headers["Authorization"] == "Bearer ORCID-TEST-ACCESS-TOKEN"
        assert "job-id" in resp.json
        assert AsyncOrcidResponse.select().exists()
        ar = AsyncOrcidResponse.get()
        assert ar.status_code == 200
        assert ar.body is not None

        job_id = str(ar.job_id)
        assert resp.json["job-id"] == job_id
        assert resp.json["response-url"].endswith(job_id)
        assert resp.headers["ORCIDHub-AsyncOperation-Response"].endswith(job_id)
        assert resp.headers["ORCIDHub-AsyncOperation-Response"] == resp.json["response-url"]

        resp = client.get(resp.json["response-url"],
                          headers=dict(authorization=f"Bearer {token.access_token}"))
        assert resp.headers["ORCIDHub-Async-Method"] == "GET"
        assert resp.headers["ORCIDHub-Async-URL"].endswith(orcid_id)

        # Incorrect request
        resp = client.get(f"/orcid/response/{uuid4()}",
                          headers=dict(authorization=f"Bearer {token.access_token}"))
        assert resp.status_code == 404

        ar.executed_at = None
        ar.save()
        resp = client.get(f"/orcid/response/{ar.job_id}",
                          headers=dict(authorization=f"Bearer {token.access_token}"))
        assert resp.status_code == 204

        ar.executed_at = datetime.utcnow()
        ar.save()
        resp = client.get(f"/orcid/response/{ar.job_id}",
                          headers=dict(authorization=f"Bearer {token.access_token}"))
        assert resp.status_code == 200

    with patch("orcid_hub.apis.requests.Session.send") as mocksend:
        mockresp = MagicMock(status_code=201, headers=headers, json={"data": "TEST"})
        mockresp.raw.stream = lambda *args, **kwargs: iter([b"""{"data": "TEST"}"""])
        mockresp.raw.headers = headers
        mocksend.return_value = mockresp
        resp = client.post(
            f"/orcid/api/v2.23/{orcid_id}/SOMETHING-MORE",
            headers=dict(authorization=f"Bearer {token.access_token}"),
            data=b"""{"data": "REQUEST"}""")
        assert resp.status_code == 201
        args, kwargs = mocksend.call_args
        assert kwargs["stream"]
        assert args[0].url == f"https://api.sandbox.orcid.org/v2.23/{orcid_id}/SOMETHING-MORE"
        assert args[0].headers["Authorization"] == "Bearer ORCID-TEST-ACCESS-TOKEN"
        assert resp.json == {"data": "TEST"}

    # malformatted ORCID ID:
    resp = client.get(
        "/orcid/api/v2.23/NOT-ORCID-ID/PATH",
        headers=dict(authorization=f"Bearer {token.access_token}"))
    assert resp.status_code == 415

    # wrong version number:
    resp = client.get(
        f"/orcid/api/v1.23_ERROR/{orcid_id}/PATH",
        headers=dict(authorization=f"Bearer {token.access_token}"))
    assert resp.status_code == 404
    assert resp.json == {
        "error": "Resource not found",
        "message": "Incorrect version: v1.23_ERROR"
    }

    # no ORCID access token
    resp = client.get(
        "/orcid/api/v2.23/0000-0000-0000-11X2/PATH",
        headers=dict(authorization=f"Bearer {token.access_token}"))
    assert resp.status_code == 403

def test_property_api(client, mocker):
    """Test property API in various formats."""
    admin = client.data.get("admin")
    resp = client.login(admin, follow_redirects=True)
    resp = client.post("/load/researcher/properties",
                       data={
                           "file_": (open(os.path.join(data_path, "properties.csv"),
                                          "rb"), "properties042.csv"),
                       },
                       follow_redirects=True)
    assert resp.status_code == 200
    assert Task.select().count() == 1

    access_token = client.get_access_token()

    resp = client.get("/api/v1/tasks?type=PROPERTY",
                      headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    task_id = int(data[0]["id"])

    resp = client.get(f"/api/v1/properties/{task_id}",
                      headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    assert len(data["records"]) == 19
    assert data["filename"] == "properties042.csv"

    del (data["id"])
    resp = client.post("/api/v1/properties/?filename=properties333.json",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(data))
    assert resp.status_code == 200
    assert Task.select().count() == 2

    records = data["records"]
    resp = client.post("/api/v1/properties/?filename=properties444.json",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 3

    resp = client.post("/api/v1/properties",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 4

    resp = client.post(f"/api/v1/properties/{task_id}",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 4

    resp = client.head(f"/api/v1/properties/{task_id}",
                       headers=dict(authorization=f"Bearer {access_token}"))
    assert "Last-Modified" in resp.headers
    assert resp.status_code == 200

    resp = client.delete(f"/api/v1/properties/{task_id}",
                         headers=dict(authorization=f"Bearer {access_token}"))

    resp = client.post("/api/v1/properties/?filename=properties333.csv",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="text/csv",
                       data=open(os.path.join(data_path, "properties.csv")).read())
    assert resp.status_code == 200
    assert Task.select().count() == 4

    resp = client.post("/api/v1/properties/?filename=properties333.json",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=open(os.path.join(data_path, "properties.json")).read())
    assert resp.status_code == 200
    assert Task.select().count() == 5
    user = User.get(orcid="0000-0000-0000-00X3")
    OrcidToken.create(user=user, org=user.organisation, scopes="/person/update")
    get_profile = mocker.patch("orcid_hub.orcid_client.MemberAPIV3.get_record",
                               return_value=get_profile_data(user=user))
    send_email = mocker.patch("orcid_hub.utils.send_email")
    create_or_update_researcher_url = mocker.patch("orcid_hub.orcid_client.MemberAPIV3.create_or_update_researcher_url")
    create_or_update_other_name = mocker.patch("orcid_hub.orcid_client.MemberAPIV3.create_or_update_other_name")
    create_or_update_address = mocker.patch("orcid_hub.orcid_client.MemberAPIV3.create_or_update_address")
    create_or_update_keyword = mocker.patch("orcid_hub.orcid_client.MemberAPIV3.create_or_update_keyword")
    for r in records:
        del(r["id"])
        r["is-active"] = True
    resp = client.post("/api/v1/properties/?filename=properties777.json",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 6
    assert UserInvitation.select().count() == 7
    get_profile.assert_called()
    send_email.assert_called()
    create_or_update_researcher_url.assert_called_once()
    create_or_update_other_name.assert_called_once()
    create_or_update_keyword.assert_called_once()
    create_or_update_address.assert_called()

def test_resource_api(client, mocker):
    """Test resource API in various formats."""
    admin = client.data.get("admin")
    resp = client.login(admin, follow_redirects=True)
    resp = client.post("/load/task/RESOURCE",
                       data={
                           "file_": (open(os.path.join(data_path, "resources.csv"),
                                          "rb"), "resources001.csv"),
                       },
                       follow_redirects=True)
    assert resp.status_code == 200
    assert Task.select().count() == 1

    access_token = client.get_access_token()

    resp = client.get("/api/v1/tasks?type=RESOURCE",
                      headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    task_id = int(data[0]["id"])

    resp = client.get(f"/api/v1/resources/{task_id}",
                      headers=dict(authorization=f"Bearer {access_token}"))
    data = json.loads(resp.data)
    assert len(data["records"]) == 2
    assert data["filename"] == "resources001.csv"

    del (data["id"])
    resp = client.post("/api/v1/resources/?filename=resources333.json",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(data))
    assert resp.status_code == 200
    assert Task.select().count() == 2

    records = data["records"]
    resp = client.post("/api/v1/resources/?filename=resources444.json",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 3
    assert Task.get(resp.json["id"]).records.count() == 2

    resp = client.post("/api/v1/resources",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 4
    assert Task.get(resp.json["id"]).records.count() == 2

    task_id = resp.json["id"]
    resp = client.post(f"/api/v1/resources/{task_id}",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 4
    assert Task.get(resp.json["id"]).records.count() == 2

    resp = client.head(f"/api/v1/resources/{task_id}",
                       headers=dict(authorization=f"Bearer {access_token}"))
    assert "Last-Modified" in resp.headers
    assert resp.status_code == 200

    resp = client.delete(f"/api/v1/resources/{task_id}",
                         headers=dict(authorization=f"Bearer {access_token}"))

    resp = client.post("/api/v1/resources/?filename=resources333.csv",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="text/csv",
                       data=readup_test_data("resources.csv", mode="r"))
    assert resp.status_code == 200
    assert Task.select().count() == 4
    data = resp.json
    records = data["records"]
    task_id = data["id"]
    assert Task.get(task_id).records.count() == 2

    resp = client.post("/api/v1/resources/?filename=resources333.json",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=readup_test_data("resources.json"))
    assert resp.status_code == 200
    assert Task.select().count() == 5
    user = User.get(orcid="0000-0000-0000-00X3")
    OrcidToken.create(user=user, org=user.organisation, scopes="/person/update")
    mocker.patch("orcid_hub.orcid_client.MemberAPIV3.get_record",
                 return_value=get_profile_data(user=user))
    get_resources = mocker.patch("orcid_hub.orcid_client.MemberAPIV3.get_resources",
                                 return_value=get_resources_data(user=user))
    send_email = mocker.patch("orcid_hub.utils.send_email")

    mocker.patch("orcid_hub.orcid_client.MemberAPIV3.put")
    post = mocker.patch("orcid_hub.orcid_client.MemberAPIV3.post")

    for r in records:
        r["is-active"] = True
        r["invitees"].append({
            "email": "new-researcher@org999.edu",
            "first-name": "FN",
            "last-name": "LN"
        })
    resp = client.post("/api/v1/resources/?filename=resources777.json",
                       headers=dict(authorization=f"Bearer {access_token}"),
                       content_type="application/json",
                       data=json.dumps(records))
    assert resp.status_code == 200
    assert Task.select().count() == 6
    assert UserInvitation.select().count() == 1
    get_resources.assert_called()
    send_email.assert_called()
    post.assert_called()

redpajama_set_name: RedPajamaGithub