# Passerelle - uniform access to data and services
# Copyright (C) 2021  Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; exclude even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a.deepcopy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


from unittest import mock

import pytest
from django.urls import reverse

import tests.utils
from passerelle.contrib.isere_ens.models import API_VERSION, IsereENS


@pytest.fixture
def setup(db):
    return tests.utils.setup_access_rights(
        IsereENS.objects.create(slug="test", base_url="https://ens38.example.net/", token="toktok")
    )


SITES_RESPONSE = """[
  {
    "name": "Save  - étangs de la Serre",
    "code": "SD29a",
    "city": "Arandon-Passins - Courtenay",
    "school_group": true,
    "social": true
  },
  {
    "name": "Save - étangs de Passins",
    "code": "SD29b",
    "city": "Arandon-Passins",
    "school_group": true,
    "social": true
  },
  {
    "name": "Save - lac de Save",
    "code": "SD29c",
    "city": "Arandon-Passins",
    "school_group": true,
    "social": false
  }
]"""

SD29B_RESPONSE = """{
  "name": "Save - étangs de Passins",
  "code": "SD29b",
  "type": "DEPARTMENTAL_ENS",
  "city": "Arandon-Passins",
  "natural_environment": "Etangs, tourbières, mares, forêts, rivière, pelouses sèches",
  "lunch_equipment": null,
  "pmr_access": "NO",
  "school_group": true,
  "social": true,
  "user_informations": "<p>Un album jeunesse a &eacute;t&eacute; r&eacute;alis&eacute; sur l'ENS de la Save. Il permet aux enfants de s'approprier le site en amont d'une sortie.</p>",
  "dogs": "LEASH",
  "educational_equipments": null,
  "eps_regulation": null,
  "main_manager": {
    "first_name": "Jo",
    "last_name": "Smith",
    "managing_entity": "Département de l'Isère",
    "main_phone": "01 23 45 67 89",
    "email": "jo.smith@example.net"
  }
}"""

SITE_404_RESPONSE = """{
  "user_message": "Impossible de trouver le site",
  "message": "Site not found with code SD29x"
}"""

ANIMATORS_RESPONSE = """[
  {
    "id": 1,
    "first_name": "Francis",
    "last_name": "Kuntz",
    "email": "fk@mail.grd",
    "phone": "123",
    "entity": "Association Nature Morte"
  },
  {
    "id": 2,
    "first_name": "Michael",
    "last_name": "Kael",
    "email": "mk@mail.grd",
    "phone": "456",
    "entity": "Association Porte de l'Enfer"
  }
]"""

SITE_CALENDAR_RESPONSE = """[
  {
    "date": "2020-01-21",
    "morning": "AVAILABLE",
    "lunch": "CLOSE",
    "afternoon": "AVAILABLE"
  },
  {
    "date": "2020-01-22",
    "morning": "AVAILABLE",
    "lunch": "OPEN",
    "afternoon": "COMPLETE"
  },
  {
    "date": "2020-01-23",
    "morning": "COMPLETE",
    "lunch": "CLOSE",
    "afternoon": "COMPLETE"
  }
]"""

BOOK_RESPONSE = """{
  "status": "BOOKING"
}"""
BOOK_RESPONSE_OVERBOOKING = """{
  "status": "OVERBOOKING"
}"""
BOOK_RESPONSE_REFUSED = """{
  "status": "REFUSED"
}"""
CANCEL_RESPONSE = """{
  "status": "CANCELED"
}"""

API_VERSIONS = [vers[1] for vers in API_VERSION]


@pytest.mark.parametrize('api_version', API_VERSIONS)
@mock.patch("passerelle.utils.Request.get")
def test_get_sites(mocked_get, app, setup, api_version):
    setup.api_version = api_version
    setup.save()
    mocked_get.return_value = tests.utils.FakedResponse(content=SITES_RESPONSE, status_code=200)
    endpoint = reverse(
        "generic-endpoint",
        kwargs={"connector": "isere-ens", "slug": setup.slug, "endpoint": "sites"},
    )
    response = app.get(endpoint)
    assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/site")
    assert mocked_get.call_args[1]["headers"]["token"] == "toktok"
    assert mocked_get.call_count == 1
    assert "data" in response.json
    assert response.json["err"] == 0
    for item in response.json["data"]:
        assert "id" in item
        assert "text" in item
        assert "city" in item
        assert "code" in item

    # test cache system
    response = app.get(endpoint)
    assert mocked_get.call_count == 1

    response = app.get(endpoint + "?q=etangs")
    assert len(response.json["data"]) == 2
    response = app.get(endpoint + "?q=CourTe")
    assert len(response.json["data"]) == 1
    response = app.get(endpoint + "?kind=social")
    assert len(response.json["data"]) == 2

    mocked_get.return_value = tests.utils.FakedResponse(content=SD29B_RESPONSE, status_code=200)
    response = app.get(endpoint + "?id=SD29b")
    assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/site/SD29b")
    assert len(response.json["data"]) == 1
    assert response.json["data"][0]["id"] == "SD29b"
    assert response.json["data"][0]["dogs"] == "LEASH"

    # bad response for ENS API
    mocked_get.return_value = tests.utils.FakedResponse(content=SITE_404_RESPONSE, status_code=404)
    response = app.get(endpoint + "?id=SD29x")
    assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/site/SD29x")
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"].startswith("error status:404")
    assert response.json["data"]["status_code"] == 404
    assert response.json["data"]["json_content"]["message"] == "Site not found with code SD29x"
    mocked_get.return_value = tests.utils.FakedResponse(content="crash", status_code=500)
    response = app.get(endpoint + "?id=foo500")
    assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/site/foo500")
    assert response.json["err"] == 1
    assert response.json["err_desc"].startswith("error status:500")
    assert response.json["err_class"].endswith("APIError")
    assert response.json["data"]["status_code"] == 500
    assert response.json["data"]["json_content"] is None
    mocked_get.return_value = tests.utils.FakedResponse(content=None, status_code=204)
    response = app.get(endpoint + "?id=foo204")
    assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/site/foo204")
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "abnormal empty response"
    mocked_get.return_value = tests.utils.FakedResponse(content="not json", status_code=200)
    response = app.get(endpoint + "?id=foo")
    assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/site/foo")
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"].startswith("invalid JSON in response:")


@pytest.mark.parametrize('api_version', API_VERSIONS)
@mock.patch("passerelle.utils.Request.get")
def test_get_animators(mocked_get, app, setup, api_version):
    setup.api_version = api_version
    setup.save()
    mocked_get.return_value = tests.utils.FakedResponse(content=ANIMATORS_RESPONSE, status_code=200)
    endpoint = reverse(
        "generic-endpoint",
        kwargs={"connector": "isere-ens", "slug": setup.slug, "endpoint": "animators"},
    )
    response = app.get(endpoint)
    assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/schoolAnimator")
    assert mocked_get.call_count == 1
    assert "data" in response.json
    assert response.json["err"] == 0
    for item in response.json["data"]:
        assert "id" in item
        assert "text" in item
        assert "first_name" in item
        assert "email" in item

    # test cache system
    response = app.get(endpoint)
    assert mocked_get.call_count == 1

    response = app.get(endpoint + "?q=Kael")
    assert len(response.json["data"]) == 1
    response = app.get(endpoint + "?q=association")
    assert len(response.json["data"]) == 2
    response = app.get(endpoint + "?q=mail.grd")
    assert len(response.json["data"]) == 2
    response = app.get(endpoint + "?id=2")
    assert len(response.json["data"]) == 1
    assert response.json["data"][0]["first_name"] == "Michael"


@pytest.mark.parametrize('api_version', API_VERSIONS)
@mock.patch("passerelle.utils.Request.get")
def test_get_site_calendar(mocked_get, app, setup, freezer, api_version):
    setup.api_version = api_version
    setup.save()
    freezer.move_to("2021-01-21 12:00:00")
    mocked_get.return_value = tests.utils.FakedResponse(content=SITE_CALENDAR_RESPONSE, status_code=200)
    endpoint = reverse(
        "generic-endpoint",
        kwargs={
            "connector": "isere-ens",
            "slug": setup.slug,
            "endpoint": "site-calendar",
        },
    )
    response = app.get(endpoint + "?site=SD29b")
    assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/site/SD29b/calendar")
    assert mocked_get.call_args[1]["params"]["start_date"] == "2021-01-21"
    assert mocked_get.call_args[1]["params"]["end_date"] == "2021-04-23"
    assert response.json["err"] == 0
    assert len(response.json["data"]) == 3

    assert response.json["data"][0]["disabled"] is False
    assert response.json["data"][1]["disabled"] is False
    assert response.json["data"][2]["disabled"] is True
    assert response.json["data"][0]["status"] == "open"
    assert response.json["data"][1]["status"] == "partially-open"
    assert response.json["data"][2]["status"] == "closed"
    assert response.json["data"][0]["details"] == "Morning (available), Lunch (closed), Afternoon (available)"
    assert response.json["data"][1]["details"] == "Morning (available), Lunch (open), Afternoon (complete)"
    assert response.json["data"][2]["details"] == "Morning (complete), Lunch (closed), Afternoon (complete)"

    # "2020-01-21"
    assert response.json["data"][0]["date_number"] == "21"
    assert response.json["data"][0]["date_weekday"] == "Tuesday"
    assert response.json["data"][0]["date_weekdayindex"] == "2"
    assert response.json["data"][0]["date_weeknumber"] == "4"
    assert response.json["data"][0]["date_month"] == "January 2020"

    response = app.get(endpoint + "?site=SD29b&start_date=2021-01-22")
    assert mocked_get.call_args[1]["params"]["start_date"] == "2021-01-22"
    assert mocked_get.call_args[1]["params"]["end_date"] == "2021-04-24"
    assert response.json["err"] == 0
    response = app.get(endpoint + "?site=SD29b&start_date=2021-01-22&end_date=2021-01-30")
    assert mocked_get.call_args[1]["params"]["start_date"] == "2021-01-22"
    assert mocked_get.call_args[1]["params"]["end_date"] == "2021-01-30"
    assert response.json["err"] == 0
    response = app.get(endpoint + "?site=SD29b&start_date=foo", status=400)
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "bad start_date format (foo), should be YYYY-MM-DD"
    response = app.get(endpoint + "?site=SD29b&end_date=bar", status=400)
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "bad end_date format (bar), should be YYYY-MM-DD"


@mock.patch("passerelle.utils.Request.post")
def test_post_book_v1(mocked_post, app, setup):
    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE, status_code=200)
    endpoint = reverse(
        "generic-endpoint",
        kwargs={
            "connector": "isere-ens",
            "slug": setup.slug,
            "endpoint": "site-booking",
        },
    )
    book = {
        "code": "resa",
        "status": "OK",
        "beneficiary_id": "42",
        "beneficiary_first_name": "Foo",
        "beneficiary_last_name": "Bar",
        "beneficiary_email": "foobar@example.net",
        "beneficiary_phone": "9876",
        "beneficiary_cellphone": "06",
        "entity_id": "38420D",
        "entity_name": "Ecole FooBar",
        "entity_type": "school",
        "project": "Publik",
        "site": "SD29b",
        "applicant": "app",
        "public": "GS",
        "date": "2020-01-22",
        "participants": "50",
        "morning": True,
        "lunch": False,
        "afternoon": False,
        "pmr": True,
        "grade_levels": ["CP", "CE1"],
        "animator": "42",
    }
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
    assert mocked_post.call_count == 1
    assert mocked_post.call_args.kwargs['json']['booking']['schoolAnimator'] == 42
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "BOOKING"

    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE_OVERBOOKING, status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
    assert mocked_post.call_count == 2
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "OVERBOOKING"

    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE_REFUSED, status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
    assert mocked_post.call_count == 3
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "booking status is REFUSED"
    assert response.json["data"]["status"] == "REFUSED"

    mocked_post.return_value = tests.utils.FakedResponse(content="""["not", "a", "dict"]""", status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
    assert mocked_post.call_count == 4
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "response is not a dict"
    assert response.json["data"] == ["not", "a", "dict"]

    mocked_post.return_value = tests.utils.FakedResponse(content="""{"foo": "bar"}""", status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
    assert mocked_post.call_count == 5
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "no status in response"
    assert response.json["data"] == {"foo": "bar"}

    book["animator"] = ""
    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE, status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
    assert 'schoolAnimator' not in mocked_post.call_args.kwargs['json']['booking']
    assert mocked_post.call_count == 6
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "BOOKING"

    del book["animator"]
    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE, status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/1.0.0/booking")
    assert 'schoolAnimator' not in mocked_post.call_args.kwargs['json']['booking']
    assert mocked_post.call_count == 7
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "BOOKING"

    del book['code']
    response = app.post_json(endpoint, params=book, status=400)
    assert response.json['err'] == 'bad-request'
    assert response.json['data'] is None
    assert response.json['err_desc'] == 'code is mandatory (API v1.0.0)'


@pytest.mark.parametrize('api_version', API_VERSIONS[1:])
@mock.patch("passerelle.utils.Request.post")
def test_post_book(mocked_post, app, setup, api_version):
    setup.api_version = api_version
    setup.save()
    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE, status_code=200)
    endpoint = reverse(
        "generic-endpoint",
        kwargs={
            "connector": "isere-ens",
            "slug": setup.slug,
            "endpoint": "site-booking",
        },
    )
    book = {
        "site": "SD29b",
        "date": "2020-01-22",
        "pmr": True,
        "morning": True,
        "lunch": False,
        "afternoon": False,
        "participants": "50",
        "animator": "42",
        "group": "3",
        "grade_levels": ["CP", "CE1"],
        "beneficiary_first_name": "Foo",
        "beneficiary_last_name": "Bar",
        "beneficiary_email": "foobar@example.net",
        "beneficiary_phone": "9876",
    }
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert mocked_post.call_count == 1
    assert mocked_post.call_args.kwargs['json']['schoolAnimator'] == 42
    assert mocked_post.call_args.kwargs['json']['participants'] == 50
    assert mocked_post.call_args.kwargs['json']['beneficiary']['lastName'] == 'Bar'
    assert mocked_post.call_args.kwargs['json']['beneficiary']['cellphone'] == ''
    assert 'idExternal' not in mocked_post.call_args.kwargs['json']
    assert 'projectCode' not in mocked_post.call_args.kwargs['json']
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "BOOKING"

    book['external_id'] = '12-34'
    book['project'] = 'pc'
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert mocked_post.call_count == 2
    assert mocked_post.call_args.kwargs['json']['idExternal'] == '12-34'
    assert mocked_post.call_args.kwargs['json']['projectCode'] == 'pc'
    assert response.json["err"] == 0

    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE_OVERBOOKING, status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert mocked_post.call_count == 3
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "OVERBOOKING"

    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE_REFUSED, status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert mocked_post.call_count == 4
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "booking status is REFUSED"
    assert response.json["data"]["status"] == "REFUSED"

    mocked_post.return_value = tests.utils.FakedResponse(content="""["not", "a", "dict"]""", status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert mocked_post.call_count == 5
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "response is not a dict"
    assert response.json["data"] == ["not", "a", "dict"]

    mocked_post.return_value = tests.utils.FakedResponse(content="""{"foo": "bar"}""", status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert mocked_post.call_count == 6
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "no status in response"
    assert response.json["data"] == {"foo": "bar"}

    book["animator"] = ""
    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE, status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert 'schoolAnimator' not in mocked_post.call_args.kwargs['json']
    assert mocked_post.call_count == 7
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "BOOKING"

    del book["animator"]
    mocked_post.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE, status_code=200)
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert 'schoolAnimator' not in mocked_post.call_args.kwargs['json']
    assert mocked_post.call_count == 8
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "BOOKING"

    book['group'] = 'G'
    response = app.post_json(endpoint, params=book, status=400)
    assert response.json['err'] == 1
    assert response.json['data'] is None
    assert response.json['err_desc'] == "group: 'G' does not match '^[0-9]*$'"

    book['group'] = ''
    response = app.post_json(endpoint, params=book, status=400)
    assert response.json['err'] == 'bad-request'
    assert response.json['data'] is None
    assert response.json['err_desc'] == 'group or applicant are mandatory (API v2.1.0)'

    del book['group']
    response = app.post_json(endpoint, params=book, status=400)
    assert response.json['err'] == 'bad-request'
    assert response.json['data'] is None
    assert response.json['err_desc'] == 'group or applicant are mandatory (API v2.1.0)'

    book['applicant'] = 'ecole 1'
    response = app.post_json(endpoint, params=book)
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school")
    assert mocked_post.call_args.kwargs['json']['schoolGroup'] is None
    assert mocked_post.call_args.kwargs['json']['applicant'] == 'ecole 1'


@pytest.mark.parametrize('api_version', API_VERSIONS)
@mock.patch("passerelle.utils.Request.get")
def test_get_booking_status(mocked_get, app, setup, api_version):
    setup.api_version = api_version
    setup.save()
    endpoint = reverse(
        "generic-endpoint",
        kwargs={
            "connector": "isere-ens",
            "slug": setup.slug,
            "endpoint": "get-site-booking",
        },
    )

    mocked_get.return_value = tests.utils.FakedResponse(content=BOOK_RESPONSE, status_code=200)
    response = app.get(endpoint + "?code=123")
    if api_version == '1.0.0':
        assert mocked_get.call_args[0][0].endswith("api/1.0.0/booking/123/status")
    else:
        assert mocked_get.call_args[0][0].endswith("api/" + api_version + "/site/booking/school/123/status/")
    assert mocked_get.call_count == 1
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "BOOKING"

    response = app.get(endpoint, status=400)  # no code specified
    assert mocked_get.call_count == 1

    mocked_get.return_value = tests.utils.FakedResponse(content="""["not", "a", "dict"]""", status_code=200)
    response = app.get(endpoint + "?code=123")
    assert mocked_get.call_count == 2
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "response is not a dict"
    assert response.json["data"] == ["not", "a", "dict"]

    mocked_get.return_value = tests.utils.FakedResponse(content="""{"foo": "bar"}""", status_code=200)
    response = app.get(endpoint + "?code=123")
    assert mocked_get.call_count == 3
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "no status in response"
    assert response.json["data"] == {"foo": "bar"}


@pytest.mark.parametrize('api_version', API_VERSIONS)
@mock.patch("passerelle.utils.Request.post")
def test_cancel_booking(mocked_post, app, setup, api_version):
    setup.api_version = api_version
    setup.save()
    endpoint = reverse(
        "generic-endpoint",
        kwargs={
            "connector": "isere-ens",
            "slug": setup.slug,
            "endpoint": "cancel-site-booking",
        },
    )
    mocked_post.return_value = tests.utils.FakedResponse(content=CANCEL_RESPONSE, status_code=200)
    response = app.post(endpoint + "?code=123")
    if api_version == '1.0.0':
        assert response.json["err"] == 1
        assert response.json["data"] is None
        assert response.json["err_desc"] == "not available on API v1.0.0"
        return
    assert mocked_post.call_args[0][0].endswith("api/" + api_version + "/site/booking/school/cancel/123")
    assert mocked_post.call_count == 1
    assert response.json["err"] == 0
    assert response.json["data"]["status"] == "CANCELED"

    response = app.post(endpoint, status=400)  # no code specified
    assert mocked_post.call_count == 1  # same as before

    mocked_post.return_value = tests.utils.FakedResponse(content="""["not", "a", "dict"]""", status_code=200)
    response = app.post(endpoint + "?code=123")
    assert mocked_post.call_count == 2
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "response is not a dict"
    assert response.json["data"] == ["not", "a", "dict"]

    mocked_post.return_value = tests.utils.FakedResponse(content="""{"foo": "bar"}""", status_code=200)
    response = app.post(endpoint + "?code=123")
    assert mocked_post.call_count == 3
    assert response.json["err"] == 1
    assert response.json["err_class"].endswith("APIError")
    assert response.json["err_desc"] == "no status in response"
    assert response.json["data"] == {"foo": "bar"}
