From 1486796d518b0cc8bbd975668de200a27bd9c299 Mon Sep 17 00:00:00 2001 From: Jeongminseo <112807942+minseo0810@users.noreply.github.com> Date: Sun, 8 Jun 2025 15:43:13 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8feat:=20=EB=89=B4=EC=8A=A4=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20API=EC=97=90=20=ED=82=A4=EC=9B=8C=EB=93=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=B0=98=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routers/news.py | 126 ++++++++++ tests/unit/routers/test_news_DB_search.py | 287 ++++++++++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 tests/unit/routers/test_news_DB_search.py diff --git a/app/routers/news.py b/app/routers/news.py index 4b1e249..6bdeaf5 100644 --- a/app/routers/news.py +++ b/app/routers/news.py @@ -6,6 +6,7 @@ import logging +from elasticsearch import Elasticsearch from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session @@ -16,6 +17,7 @@ router = APIRouter() db_dependency = Depends(db_manager.get_db) # 전역 변수로 설정 +es = Elasticsearch("http://elasticsearch:9200") @router.get("/recommend") @@ -91,3 +93,127 @@ def get_news_recommendations( return { "results": results } + +@router.get("/DB_search") +def search_news_by_keyword( + user_id: str = Query(..., description="사용자 ID"), + keyword: str = Query(..., description="검색할 카테고리 키워드 (예: '보건', '환경')"), + limit: int = Query(10, ge=1, le=100, description="검색 결과 최대 개수 (기본값: 10, 최대: 100)"), + db: Session = db_dependency +): + """ + 사용자가 입력한 키워드를 기반으로 가장 유사한 뉴스 카테고리를 찾고, + 해당 카테고리에 속한 뉴스 기사를 반환합니다. + + Args: + user_id (str): 검색을 수행할 사용자 ID. + keyword (str): 검색 키워드 (카테고리명). + limit (int): 반환할 채용 공고 수 (기본값: 10, 최대 100). + db (Session): 데이터베이스 세션 객체. + + Returns: + dict: { + "matched_category": str, + "results": List[dict] + } + """ + # ✅ 1. 사용자 존재 여부 확인 + user = db.query(Users).filter(Users.user_id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="User not found") + + try: + # ✅ 2-1. match_phrase_prefix로 후보군 검색 (자동완성 역할) + prefix_result = es.search( + index="categories", + body={ + "size": 10, + "query": { + "bool": { + "must": { + "match_phrase_prefix": { + "category_name": {"query": keyword} + } + }, + "filter": { + "term": { + "feature": "news" + } + } + } + } + } + ) + prefix_hits = prefix_result.get("hits", {}).get("hits", []) + if not prefix_hits: + # 후보군 없으면 바로 기타 처리 + matched_category = "기타" + category_id = 0 + else: + candidate_names = [hit["_source"]["category_name"] for hit in prefix_hits] + + # ✅ 2-2. BM25 기반 match 쿼리로 후보군 중 가장 유사한 카테고리 검색 + bm25_result = es.search( + index="categories", + body={ + "size": 1, + "query": { + "bool": { + "must": { + "match": { + "category_name": { + "query": keyword, + "operator": "and" + } + } + }, + "filter": { + "terms": { + "category_name.keyword": candidate_names # 후보군 필터링 + } + } + } + } + } + ) + bm25_hits = bm25_result.get("hits", {}).get("hits", []) + if bm25_hits: + matched_source = bm25_hits[0]["_source"] + matched_category = matched_source["category_name"] + category_id = matched_source["category_id"] + else: + # BM25가 후보군 중 적합한 것을 못 찾으면 prefix 후보 중 1순위 반환 + matched_category = prefix_hits[0]["_source"]["category_name"] + category_id = prefix_hits[0]["_source"]["category_id"] + + except ConnectionError as e: + raise HTTPException(status_code=500, detail="Elasticsearch 연결 실패") from e + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) from e + + # ✅ 3. 해당 카테고리의 뉴스 조회 + news_list = ( + db.query(News) + .filter(News.category_id == category_id) + .order_by(News.publish_date.desc()) + .limit(limit) + .all() + ) + + # ✅ 4. 뉴스 결과 정리 + results = [ + { + "title": news.title, + "source": news.source, + "publish_date": news.publish_date.isoformat(), # date → 문자열 (ISO 포맷) + "url": news.url, + "original_url": news.original_url + } + for news in news_list + ] + + # ✅ 5. 최종 응답 반환 + return { + "matched_category": matched_category, + "results": results + } \ No newline at end of file diff --git a/tests/unit/routers/test_news_DB_search.py b/tests/unit/routers/test_news_DB_search.py new file mode 100644 index 0000000..85f0810 --- /dev/null +++ b/tests/unit/routers/test_news_DB_search.py @@ -0,0 +1,287 @@ +import datetime +from unittest.mock import patch + +import pytest +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.sql import text + +from app.main import app +from app.models import ( + Base, + Category, + Feature, + News, + Users, +) +from app.utils.db_manager import db_manager + +# 테스트용 SQLite DB 설정 +TEST_DATABASE_URL = "sqlite:///./test_news.db" +engine = create_engine(TEST_DATABASE_URL, connect_args={"check_same_thread": False}) +TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# DB 초기화 및 테이블 생성 +@pytest.fixture(scope="function") +def setup_database(): + """ + 테스트용 데이터베이스를 초기화하고, 모든 테이블을 생성합니다. + + 이 fixture는 테스트 함수 실행 전 매번 호출되어 데이터베이스를 깨끗한 상태로 초기화합니다. + 외래 키 제약 조건을 활성화하고, 기존 테이블은 모두 삭제 후 다시 생성합니다. + + Returns: + None + """ + with engine.connect() as conn: + conn.execute(text("PRAGMA foreign_keys = ON;")) # 외래 키 활성화 + Base.metadata.drop_all(bind=conn) # 기존 테이블 삭제 + Base.metadata.create_all(bind=conn) # 테이블 생성 + +@pytest.fixture(scope="function") +def test_db(setup_database): + """ + 테스트용 DB 세션을 생성하고 초기 데이터를 삽입합니다. + + setup_database fixture를 사용하여 DB를 초기화한 후, 테스트에 필요한 + 사용자, 기능, 카테고리, 채용 공고 등의 기본 데이터를 삽입합니다. + + 테스트 종료 시 세션을 롤백하고 종료하며, 모든 테이블을 삭제합니다. + + Yields: + Session: 테스트용 SQLAlchemy DB 세션 + """ + db = TestingSessionLocal() + + # 기존 데이터 삭제 + db.query(Category).delete() + db.query(Feature).delete() + db.query(Users).delete() + db.commit() + + user = Users(user_id="user123", user_name="홍길동") + feature = Feature(feature_type="news") + db.add_all([user, feature]) + db.commit() + + db.refresh(user) + db.refresh(feature) + + category = Category(feature_id=1, category_name="보건") + db.add(category) + db.commit() + db.refresh(category) + + news_item = News( + news_id=1, + category_id=category.category_id, + title="테스트 뉴스", + contents="테스트 뉴스 내용", + source="테스트 기관", + publish_date=datetime.datetime(2023, 6, 7, 9, 51), + category="기타", + url="http://testnews.com/article", + original_url="http://originalsource.com/article" + ) + db.add(news_item) + db.commit() + db.refresh(news_item) + + yield db + + db.commit() + db.rollback() # 테스트 종료 후 변경 사항 되돌리기 + db.close() # 세션 종료 + Base.metadata.drop_all(bind=engine) # 테스트 끝나면 DB 초기화 + +def override_get_db(): + """ + FastAPI 의존성 오버라이드용 함수. + + 테스트용 DB 세션을 생성하여 반환하며, + 사용 후 세션을 안전하게 종료합니다. + + Yields: + Session: 테스트용 SQLAlchemy DB 세션 + """ + db = TestingSessionLocal() + try: + yield db + finally: + db.close() + +app.dependency_overrides[db_manager.get_db] = override_get_db + +@pytest.fixture(scope="function") +def client(): + """ + FastAPI 테스트 클라이언트를 생성하여 반환합니다. + + Returns: + TestClient: FastAPI 테스트 클라이언트 인스턴스 + """ + return TestClient(app) + +# 정상 검색 테스트 +@patch("app.routers.news.es.search") +def test_search_news_success(mock_es_search, client, test_db): + """ + Elasticsearch에서 정상적으로 카테고리 검색 결과를 받아 + 관련 채용 공고가 올바르게 반환되는지 검증하는 테스트입니다. + """ + mock_es_search.side_effect = [ + # match_phrase_prefix 응답 + { + "hits": { + "hits": [ + { + "_source": { + "category_name": "보건", + "category_id": 1, + "feature": "news" + } + } + ] + } + }, + # BM25 응답 + { + "hits": { + "hits": [ + { + "_source": { + "category_name": "보건", + "category_id": 1, + "feature": "news" + } + } + ] + } + } + ] + + response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건", "limit": 5}) + assert response.status_code == 200 + data = response.json() + assert data["matched_category"] == "보건" + assert len(data["results"]) == 1 + assert data["results"][0]["title"] == "보건 정책 변화" + assert data["results"][0]["source"] == "뉴스타임" + +# 🔹 사용자 미존재 테스트 +def test_search_news_user_not_found(client, setup_database): + """ + 존재하지 않는 사용자 ID로 검색 요청 시 + 404 에러 및 'User not found' 메시지가 반환되는지 확인합니다. + """ + response = client.get("/news/DB_search", params={"user_id": "unknown", "keyword": "보건"}) + assert response.status_code == 404 + assert response.json()["detail"] == "User not found" + +# 🔹 Elasticsearch에서 카테고리 미검색 시 기본값 처리 테스트 +@patch("app.routers.news.es.search") +def test_search_news_no_matching_category(mock_es_search, client, test_db): + """ + Elasticsearch에서 키워드에 해당하는 카테고리가 검색되지 않을 때 + 기본값 '기타' 카테고리로 처리되고 빈 결과가 반환되는지 검증합니다. + """ + mock_es_search.return_value = { + "hits": {"hits": []} # match_phrase_prefix 결과 없음 + } + response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "미확인키워드"}) + assert response.status_code == 200 + data = response.json() + assert data["matched_category"] == "기타" + assert data["results"] == [] + +# 🔹 해당 카테고리에 뉴스가 없을 경우 +@patch("app.routers.news.es.search") +def test_search_news_category_not_in_db(mock_es_search, client, test_db): + """ + Elasticsearch에서는 검색 결과를 반환했지만, + 해당 category_id가 DB에 존재하지 않는 경우 빈 결과를 반환하는지 확인. + """ + mock_es_search.return_value = { + "hits": { + "hits": [ + { + "_source": { + "category_name": "존재하지않음", + "category_id": 9999, # DB에 존재하지 않음 + "feature": "news" + } + } + ] + } + } + + response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "존재하지않음"}) + assert response.status_code == 200 + data = response.json() + assert data["matched_category"] == "존재하지않음" + assert data["results"] == [] + +# 🔹 news API limit 파라미터 테스트 (최대 100, 기본 10 등) +@patch("app.routers.news.es.search") +def test_search_news_limit_param(mock_es_search, client, test_db): + """ + 뉴스 검색 시 limit 파라미터의 기본값, 지정값, 최대값 초과 시 + 각각 올바르게 처리되는지 검증합니다. + - 기본 limit 사용 (10) + - limit 지정 사용 + - 최대값(100) 초과 시 422 에러 발생 확인 + """ + mock_es_search.return_value = { + "hits": { + "hits": [ + { + "_source": { + "category_name": "보건", + "category_id": 1, + "feature": "news" + } + } + ] + } + } + + # 기본 limit=10 + response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건"}) + assert response.status_code == 200 + data = response.json() + assert "results" in data + + # limit=1 지정 + response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건", "limit": 1}) + assert response.status_code == 200 + data = response.json() + assert len(data["results"]) <= 1 + + # limit가 최대 100 초과 시도 + response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건", "limit": 101}) + assert response.status_code == 422 # 유효성 검증 실패 + + +# 🔹 news API Elasticsearch 연결 실패 예외 테스트 +@patch("app.routers.news.es.search", side_effect=ConnectionError) +def test_search_news_es_connection_error(mock_es_search, client, test_db): + """ + Elasticsearch 연결 실패 시 + 500 에러와 'Elasticsearch 연결 실패' 메시지가 반환되는지 확인합니다. + """ + response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건"}) + assert response.status_code == 500 + assert response.json() == {"detail": "Elasticsearch 연결 실패"} + + +# 🔹 news API Elasticsearch 기타 예외 테스트 +@patch("app.routers.news.es.search", side_effect=Exception("ES 오류")) +def test_search_news_es_other_error(mock_es_search, client, test_db): + """ + Elasticsearch 검색 중 알 수 없는 예외 발생 시 + 500 에러와 예외 메시지가 포함된 응답이 반환되는지 검증합니다. + """ + response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건"}) + assert response.status_code == 500 + assert "ES 오류" in response.json()["detail"] \ No newline at end of file From 2002d48a0a10e7b81315b20a1f40dd92a30cd437 Mon Sep 17 00:00:00 2001 From: Jeongminseo <112807942+minseo0810@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:04:35 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8fix:=20=EB=89=B4=EC=8A=A4=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20API=20=EB=AC=B8=EC=84=9C=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routers/news.py | 7 +- tests/unit/routers/test_news_DB_search.py | 119 ++++++++++------------ 2 files changed, 57 insertions(+), 69 deletions(-) diff --git a/app/routers/news.py b/app/routers/news.py index 6bdeaf5..df4c061 100644 --- a/app/routers/news.py +++ b/app/routers/news.py @@ -102,9 +102,8 @@ def search_news_by_keyword( db: Session = db_dependency ): """ - 사용자가 입력한 키워드를 기반으로 가장 유사한 뉴스 카테고리를 찾고, - 해당 카테고리에 속한 뉴스 기사를 반환합니다. - + 사용자가 입력한 키워드를 기반으로 가장 유사한 뉴스 카테고리를 찾고, 해당 카테고리에 속한 뉴스 기사를 반환합니다. + Args: user_id (str): 검색을 수행할 사용자 ID. keyword (str): 검색 키워드 (카테고리명). @@ -216,4 +215,4 @@ def search_news_by_keyword( return { "matched_category": matched_category, "results": results - } \ No newline at end of file + } diff --git a/tests/unit/routers/test_news_DB_search.py b/tests/unit/routers/test_news_DB_search.py index 85f0810..6f7ff79 100644 --- a/tests/unit/routers/test_news_DB_search.py +++ b/tests/unit/routers/test_news_DB_search.py @@ -18,7 +18,7 @@ from app.utils.db_manager import db_manager # 테스트용 SQLite DB 설정 -TEST_DATABASE_URL = "sqlite:///./test_news.db" +TEST_DATABASE_URL = "sqlite:///./test.db" engine = create_engine(TEST_DATABASE_URL, connect_args={"check_same_thread": False}) TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) @@ -39,6 +39,7 @@ def setup_database(): Base.metadata.drop_all(bind=conn) # 기존 테이블 삭제 Base.metadata.create_all(bind=conn) # 테이블 생성 +# 테스트용 DB 세션 및 테스트 데이터 삽입 @pytest.fixture(scope="function") def test_db(setup_database): """ @@ -53,22 +54,25 @@ def test_db(setup_database): Session: 테스트용 SQLAlchemy DB 세션 """ db = TestingSessionLocal() - + # 기존 데이터 삭제 db.query(Category).delete() db.query(Feature).delete() db.query(Users).delete() db.commit() - user = Users(user_id="user123", user_name="홍길동") - feature = Feature(feature_type="news") - db.add_all([user, feature]) + # ✅ 테스트 데이터 삽입 + user = Users(user_id="user123", user_name="John Doe") + db.add(user) db.commit() + db.refresh(user) # ✅ user_id 생성 확인 - db.refresh(user) - db.refresh(feature) + feature = Feature(feature_type="News") + db.add(feature) + db.commit() + db.refresh(feature) # ✅ id 생성 확인 - category = Category(feature_id=1, category_name="보건") + category = Category(feature_id=feature.feature_id, category_name="보건") db.add(category) db.commit() db.refresh(category) @@ -76,9 +80,9 @@ def test_db(setup_database): news_item = News( news_id=1, category_id=category.category_id, - title="테스트 뉴스", + title="보건 정책 변화", contents="테스트 뉴스 내용", - source="테스트 기관", + source="뉴스타임", publish_date=datetime.datetime(2023, 6, 7, 9, 51), category="기타", url="http://testnews.com/article", @@ -95,6 +99,7 @@ def test_db(setup_database): db.close() # 세션 종료 Base.metadata.drop_all(bind=engine) # 테스트 끝나면 DB 초기화 +# ✅ FastAPI 의존성 오버라이드 설정 def override_get_db(): """ FastAPI 의존성 오버라이드용 함수. @@ -113,6 +118,7 @@ def override_get_db(): app.dependency_overrides[db_manager.get_db] = override_get_db +# FastAPI 테스트 클라이언트 @pytest.fixture(scope="function") def client(): """ @@ -131,32 +137,26 @@ def test_search_news_success(mock_es_search, client, test_db): 관련 채용 공고가 올바르게 반환되는지 검증하는 테스트입니다. """ mock_es_search.side_effect = [ - # match_phrase_prefix 응답 { "hits": { - "hits": [ - { - "_source": { - "category_name": "보건", - "category_id": 1, - "feature": "news" - } + "hits": [{ + "_source": { + "category_name": "보건", + "category_id": 1, + "feature": "news" } - ] + }] } }, - # BM25 응답 { "hits": { - "hits": [ - { - "_source": { - "category_name": "보건", - "category_id": 1, - "feature": "news" - } + "hits": [{ + "_source": { + "category_name": "보건", + "category_id": 1, + "feature": "news" } - ] + }] } } ] @@ -169,7 +169,7 @@ def test_search_news_success(mock_es_search, client, test_db): assert data["results"][0]["title"] == "보건 정책 변화" assert data["results"][0]["source"] == "뉴스타임" -# 🔹 사용자 미존재 테스트 +# ✅ 사용자 미존재 테스트 def test_search_news_user_not_found(client, setup_database): """ 존재하지 않는 사용자 ID로 검색 요청 시 @@ -179,23 +179,21 @@ def test_search_news_user_not_found(client, setup_database): assert response.status_code == 404 assert response.json()["detail"] == "User not found" -# 🔹 Elasticsearch에서 카테고리 미검색 시 기본값 처리 테스트 +# ✅ 키워드 미일치 → 기본 카테고리 '기타' 처리 @patch("app.routers.news.es.search") def test_search_news_no_matching_category(mock_es_search, client, test_db): """ Elasticsearch에서 키워드에 해당하는 카테고리가 검색되지 않을 때 기본값 '기타' 카테고리로 처리되고 빈 결과가 반환되는지 검증합니다. """ - mock_es_search.return_value = { - "hits": {"hits": []} # match_phrase_prefix 결과 없음 - } + mock_es_search.return_value = {"hits": {"hits": []}} response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "미확인키워드"}) assert response.status_code == 200 data = response.json() assert data["matched_category"] == "기타" assert data["results"] == [] -# 🔹 해당 카테고리에 뉴스가 없을 경우 +# ✅ ES는 결과 줬지만 DB에 해당 category_id가 없음 @patch("app.routers.news.es.search") def test_search_news_category_not_in_db(mock_es_search, client, test_db): """ @@ -204,25 +202,22 @@ def test_search_news_category_not_in_db(mock_es_search, client, test_db): """ mock_es_search.return_value = { "hits": { - "hits": [ - { - "_source": { - "category_name": "존재하지않음", - "category_id": 9999, # DB에 존재하지 않음 - "feature": "news" - } + "hits": [{ + "_source": { + "category_name": "존재하지않음", + "category_id": 9999, + "feature": "news" } - ] + }] } } - response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "존재하지않음"}) assert response.status_code == 200 data = response.json() assert data["matched_category"] == "존재하지않음" assert data["results"] == [] -# 🔹 news API limit 파라미터 테스트 (최대 100, 기본 10 등) +# ✅ limit 파라미터 유효성 검증 @patch("app.routers.news.es.search") def test_search_news_limit_param(mock_es_search, client, test_db): """ @@ -234,36 +229,31 @@ def test_search_news_limit_param(mock_es_search, client, test_db): """ mock_es_search.return_value = { "hits": { - "hits": [ - { - "_source": { - "category_name": "보건", - "category_id": 1, - "feature": "news" - } + "hits": [{ + "_source": { + "category_name": "보건", + "category_id": 1, + "feature": "news" } - ] + }] } } - # 기본 limit=10 + # 기본 limit = 10 response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건"}) assert response.status_code == 200 - data = response.json() - assert "results" in data + assert "results" in response.json() - # limit=1 지정 + # limit = 1 response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건", "limit": 1}) assert response.status_code == 200 - data = response.json() - assert len(data["results"]) <= 1 + assert len(response.json()["results"]) <= 1 - # limit가 최대 100 초과 시도 + # limit > 100 초과 → 422 response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건", "limit": 101}) - assert response.status_code == 422 # 유효성 검증 실패 - + assert response.status_code == 422 -# 🔹 news API Elasticsearch 연결 실패 예외 테스트 +# ✅ Elasticsearch 연결 실패 시 @patch("app.routers.news.es.search", side_effect=ConnectionError) def test_search_news_es_connection_error(mock_es_search, client, test_db): """ @@ -274,8 +264,7 @@ def test_search_news_es_connection_error(mock_es_search, client, test_db): assert response.status_code == 500 assert response.json() == {"detail": "Elasticsearch 연결 실패"} - -# 🔹 news API Elasticsearch 기타 예외 테스트 +# ✅ Elasticsearch 기타 예외 발생 시 @patch("app.routers.news.es.search", side_effect=Exception("ES 오류")) def test_search_news_es_other_error(mock_es_search, client, test_db): """ @@ -284,4 +273,4 @@ def test_search_news_es_other_error(mock_es_search, client, test_db): """ response = client.get("/news/DB_search", params={"user_id": "user123", "keyword": "보건"}) assert response.status_code == 500 - assert "ES 오류" in response.json()["detail"] \ No newline at end of file + assert "ES 오류" in response.json()["detail"] From dddb54924691577a96092400ec4140744fd59824 Mon Sep 17 00:00:00 2001 From: Jeongminseo <112807942+minseo0810@users.noreply.github.com> Date: Mon, 9 Jun 2025 15:43:56 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8feat:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=EC=97=90=20?= =?UTF-8?q?=EB=89=B4=EC=8A=A4=20DB=20=EA=B2=80=EC=83=89=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 37700d8..1009509 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ FastAPI와 MySQL을 사용한 카카오톡 챗봇 개발을 위한 템플릿 프 │ ├── test_get_my_category_list.py │ ├── test_get_news_recommendations.py │ ├── test_news_client.py +│ ├── test_news_DB_search.py │ │── test_user_delete_favorite.py │ └── utils/ │ ├── response_builder/