Skip to main content
Glama
test_api_client.py22.6 kB
import pytest import respx import httpx from urllib.parse import urlencode, quote from mcp_tourism.api_client import KoreaTourismApiClient, LANGUAGE_SERVICE_MAP # Sample successful response structure from the API MOCK_SUCCESS_RESPONSE = { "response": { "header": {"resultCode": "0000", "resultMsg": "OK"}, "body": { "items": { "item": [ { "addr1": "161 Sajik-ro, Jongno-gu, Seoul", "addr2": "", "areacode": "1", "cat1": "A02", "cat2": "A0201", "cat3": "A02010100", "contentid": "264337", "contenttypeid": "76", "createdtime": "20110226022850", "firstimage": "http://tong.visitkorea.or.kr/cms/resource/33/2678633_image2_1.jpg", "firstimage2": "http://tong.visitkorea.or.kr/cms/resource/33/2678633_image3_1.jpg", "cpyrhtDivCd": "Type3", "mapx": "126.9769930325", "mapy": "37.5788222356", "mlevel": "6", "modifiedtime": "20241010094040", "sigungucode": "23", "tel": "+82-2-3700-3900", "title": "Gyeongbokgung Palace (경복궁)", } ] }, "numOfRows": 1, "pageNo": 1, "totalCount": 1, }, } } # Sample error response structure MOCK_ERROR_RESPONSE = { "response": { "header": {"resultCode": "99", "resultMsg": "SERVICE ERROR"}, "body": {}, } } @pytest.mark.asyncio @respx.mock async def test_search_by_keyword_success(client: KoreaTourismApiClient): """Tests successful search_by_keyword call.""" keyword = "Gyeongbokgung" area_code = "1" language = "en" api_key_encoded = quote( client.api_key, safe="" ) # API key should be URL encoded in the request # Construct expected params and URL expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "keyword": keyword, "arrange": "Q", "listYN": "Y", "areaCode": area_code, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.SEARCH_KEYWORD_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" # Mock the specific request respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) # Call the method results = await client.search_by_keyword( keyword=keyword, area_code=area_code, language=language ) # Assertions assert results["total_count"] == 1 assert len(results["items"]) == 1 assert results["items"][0]["title"] == "Gyeongbokgung Palace (경복궁)" assert results["items"][0]["contentid"] == "264337" @pytest.mark.asyncio @respx.mock async def test_search_by_keyword_different_language(client: KoreaTourismApiClient): """Tests search_by_keyword with a language different from the client's default.""" keyword = "경복궁" language = "ko" # Assuming 'ko' is supported and mapped in LANGUAGE_SERVICE_MAP # Need to add 'ko': 'KorService1' to LANGUAGE_SERVICE_MAP for this test LANGUAGE_SERVICE_MAP["ko"] = "KorService1" # Temporarily add for test api_key_encoded = quote(client.api_key, safe="") # Construct expected params and URL for the Korean service expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "keyword": keyword, "arrange": "Q", "listYN": "Y", } encoded_params = urlencode(expected_params) # Note the use of LANGUAGE_SERVICE_MAP['ko'] expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.SEARCH_KEYWORD_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" # Mock the request to the Korean service endpoint respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) # Use same mock response for simplicity # Call the method with the specific language results = await client.search_by_keyword(keyword=keyword, language=language) # Assertions (check if the structure is correct, content might differ for 'ko') assert "total_count" in results assert "items" in results del LANGUAGE_SERVICE_MAP["ko"] # Clean up temp addition @pytest.mark.asyncio @respx.mock async def test_search_by_keyword_caching(client: KoreaTourismApiClient): """Tests if responses are cached.""" keyword = "Namsan" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "keyword": keyword, "arrange": "Q", "listYN": "Y", } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.SEARCH_KEYWORD_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" # Mock the request route = respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) # First call - should hit the API await client.search_by_keyword(keyword=keyword, language=language) assert route.called is True assert route.call_count == 1 # Second call - should hit the cache await client.search_by_keyword(keyword=keyword, language=language) assert route.call_count == 1 # Call count should not increase @pytest.mark.asyncio @respx.mock async def test_api_error_handling(client: KoreaTourismApiClient): """Tests error handling for non-200 responses.""" keyword = "ErrorTest" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "keyword": keyword, "arrange": "Q", "listYN": "Y", } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.SEARCH_KEYWORD_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" # Mock a 500 server error response respx.get(expected_url).mock( return_value=httpx.Response(500, json=MOCK_ERROR_RESPONSE) ) # Check if the correct exception is raised (requires importing the exception) from mcp_tourism.api_client import TourismApiServerError with pytest.raises(TourismApiServerError): await client.search_by_keyword(keyword=keyword, language=language) @pytest.mark.asyncio @respx.mock async def test_get_area_based_list_success(client: KoreaTourismApiClient): """Tests successful get_area_based_list call.""" area_code = "1" content_type_id = "76" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "arrange": "Q", "listYN": "Y", "areaCode": area_code, "contentTypeId": content_type_id, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.AREA_BASED_LIST_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_area_based_list( area_code=area_code, content_type_id=content_type_id, language=language ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_location_based_list_success(client: KoreaTourismApiClient): """Tests successful get_location_based_list call.""" mapx = 126.9780 mapy = 37.5665 radius = 1000 language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "arrange": "Q", "listYN": "Y", "mapX": str(mapx), "mapY": str(mapy), "radius": str(radius), } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.LOCATION_BASED_LIST_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_location_based_list( mapx=mapx, mapy=mapy, radius=radius, language=language ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_search_festival_success(client: KoreaTourismApiClient): """Tests successful search_festival call.""" start_date = "20250501" end_date = "20250531" area_code = "1" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "eventStartDate": start_date, "eventEndDate": end_date, "areaCode": area_code, "arrange": "Q", "listYN": "Y", } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.SEARCH_FESTIVAL_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.search_festival( event_start_date=start_date, event_end_date=end_date, area_code=area_code, language=language, ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_search_stay_success(client: KoreaTourismApiClient): """Tests successful search_stay call.""" area_code = "1" sigungu_code = "23" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "areaCode": area_code, "sigunguCode": sigungu_code, "arrange": "Q", "listYN": "Y", } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.SEARCH_STAY_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.search_stay( area_code=area_code, sigungu_code=sigungu_code, language=language ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_detail_common_success(client: KoreaTourismApiClient): """Tests successful get_detail_common call.""" content_id = "264337" content_type_id = "76" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "contentId": content_id, "contentTypeId": content_type_id, "defaultYN": "Y", "firstImageYN": "Y", "areacodeYN": "Y", "catcodeYN": "Y", "addrinfoYN": "Y", "mapinfoYN": "Y", "overviewYN": "Y", "transGuideYN": "Y", } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.DETAIL_COMMON_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_detail_common( content_id=content_id, content_type_id=content_type_id, language=language ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_detail_images_success(client: KoreaTourismApiClient): """Tests successful get_detail_images call.""" content_id = "264337" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "contentId": content_id, "imageYN": "Y", "subImageYN": "Y", } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.DETAIL_IMAGE_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_detail_images(content_id=content_id, language=language) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_detail_intro_success(client: KoreaTourismApiClient): """Tests successful get_detail_intro call.""" content_id = "264337" content_type_id = "76" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "contentId": content_id, "contentTypeId": content_type_id, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.DETAIL_INTRO_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_detail_intro( content_id=content_id, content_type_id=content_type_id, language=language ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_detail_info_success(client: KoreaTourismApiClient): """Tests successful get_detail_info call.""" content_id = "264337" content_type_id = "76" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "contentId": content_id, "contentTypeId": content_type_id, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.DETAIL_INFO_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_detail_info( content_id=content_id, content_type_id=content_type_id, language=language ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_area_code_list_top_level(client: KoreaTourismApiClient): """Tests successful get_area_code_list for top-level areas.""" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.AREA_CODE_LIST_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_area_code_list(language=language) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_area_code_list_sub_level(client: KoreaTourismApiClient): """Tests successful get_area_code_list for sub-level areas (sigungu).""" area_code = "1" # Seoul language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "areaCode": area_code, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.AREA_CODE_LIST_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_area_code_list(area_code=area_code, language=language) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_category_code_list_success(client: KoreaTourismApiClient): """Tests successful get_category_code_list call.""" content_type_id = "76" language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "contentTypeId": content_type_id, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.CATEGORY_CODE_LIST_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_category_code_list( content_type_id=content_type_id, language=language ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_search_by_keyword_with_sigungu(client: KoreaTourismApiClient): """Tests search_by_keyword with area_code and sigungu_code.""" keyword = "Restaurant" area_code = "1" # Seoul sigungu_code = "23" # Jongno-gu language = "en" api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "keyword": keyword, "arrange": "Q", "listYN": "Y", "areaCode": area_code, "sigunguCode": sigungu_code, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.SEARCH_KEYWORD_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.search_by_keyword( keyword=keyword, area_code=area_code, sigungu_code=sigungu_code, language=language, ) assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_area_based_list_pagination(client: KoreaTourismApiClient): """Tests get_area_based_list with pagination parameters.""" area_code = "1" language = "en" page = 2 rows = 5 api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": str(rows), "pageNo": str(page), "_type": "json", "arrange": "Q", "listYN": "Y", "areaCode": area_code, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.AREA_BASED_LIST_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" # Modify mock response slightly to reflect pagination if needed, or use generic one mock_paginated_response = MOCK_SUCCESS_RESPONSE.copy() # Deep copy nested dictionaries to avoid modifying the original MOCK_SUCCESS_RESPONSE mock_paginated_response["response"] = MOCK_SUCCESS_RESPONSE["response"].copy() mock_paginated_response["response"]["body"] = MOCK_SUCCESS_RESPONSE["response"][ "body" ].copy() mock_paginated_response["response"]["body"]["pageNo"] = page mock_paginated_response["response"]["body"]["numOfRows"] = rows respx.get(expected_url).mock( return_value=httpx.Response(200, json=mock_paginated_response) ) results = await client.get_area_based_list( area_code=area_code, language=language, page=page, rows=rows ) assert results["page_no"] == page assert results["num_of_rows"] == rows assert "total_count" in results assert "items" in results @pytest.mark.asyncio @respx.mock async def test_get_category_code_list_sub_category(client: KoreaTourismApiClient): """Tests get_category_code_list for sub-categories (cat2).""" language = "en" cat1 = "A01" # Example: Nature category api_key_encoded = quote(client.api_key, safe="") expected_params = { "MobileOS": "ETC", "MobileApp": "MobileApp", "numOfRows": "20", "pageNo": "1", "_type": "json", "cat1": cat1, } encoded_params = urlencode(expected_params) expected_url = f"{client.BASE_URL}/{LANGUAGE_SERVICE_MAP[language]}{client.CATEGORY_CODE_LIST_ENDPOINT}?serviceKey={api_key_encoded}&{encoded_params}" respx.get(expected_url).mock( return_value=httpx.Response(200, json=MOCK_SUCCESS_RESPONSE) ) results = await client.get_category_code_list(cat1=cat1, language=language) assert "total_count" in results assert "items" in results # TODO: Add more tests for specific parameter combinations if needed. # TODO: Consider adding tests for rate limiting and retry behavior, although they can be complex to mock accurately.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/harimkang/mcp-korea-tourism-api'

If you have feedback or need assistance with the MCP directory API, please join our Discord server