Skip to content

Commit ed3b34e

Browse files
Merge pull request #119 from snuhcs-course/fix_test_backend
Fix test backend
2 parents 92f6dc9 + 95c7b74 commit ed3b34e

44 files changed

Lines changed: 82 additions & 9108 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Node.js dependencies
44
/sumdays-backend/node_modules
5+
/sumdays-backend/coverage
56

67
# Byte-compiled / optimal files
78
**/.__pytest_cache/
@@ -13,5 +14,5 @@
1314
# env
1415
**/.env
1516
**/.coverage
16-
**/coverage/
17+
**/coverage
1718
**/tmp/

Sumdays/.idea/deploymentTargetSelector.xml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sumdays-backend/ai/requirements.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
--extra-index-url https://download.pytorch.org/whl/cpu
2+
torch
3+
sentence-transformers
4+
15
langchain==0.3.27
26
langchain-openai==0.3.35
37
langchain-core==0.3.79
@@ -10,5 +14,4 @@ flask
1014
google-cloud-vision
1115
pytest
1216
coverage
13-
pytest-cov
14-
sentence-transformers
17+
pytest-cov

sumdays-backend/ai/tests/conftest.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313

1414
@pytest.fixture(scope="session", autouse=True)
1515
def load_env():
16-
"""
17-
.env 파일을 명시적으로 로드 (테스트 실행 시 환경 변수 누락 방지)
18-
"""
1916
env_path = os.path.join(os.path.dirname(__file__), "..", "..", ".env")
2017
if os.path.exists(env_path):
2118
load_dotenv(dotenv_path=env_path)
@@ -26,11 +23,6 @@ def load_env():
2623

2724
@pytest.fixture
2825
def client():
29-
"""
30-
Flask 애플리케이션의 테스트 클라이언트를 반환합니다.
31-
- 실제 app.py를 import하여 모든 blueprint를 등록한 상태로 실행됩니다.
32-
- 내부적으로 요청 시 실제 OpenAI/Google Vision API가 호출됩니다.
33-
"""
3426
app.config["TESTING"] = True
3527
app.config["WTF_CSRF_ENABLED"] = False
3628
return app.test_client()

sumdays-backend/ai/tests/test_diary_service.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ def test_analyze_diary_integration(client):
1616
assert response.status_code == 200
1717
data = response.get_json()
1818

19-
# JSON 필드 검증
2019
assert "analysis" in data
2120
assert "icon" in data
2221
assert "ai_comment" in data
@@ -25,9 +24,6 @@ def test_analyze_diary_integration(client):
2524
assert "emotion_score" in data["analysis"]
2625

2726
def test_analysis_diary_missing_field(client):
28-
"""
29-
[에러 테스트] /analysis/diary - 빈 바디 전달
30-
"""
3127
res = client.post("/analysis/diary", data=json.dumps({}),
3228
content_type="application/json")
3329
data = res.get_json()

sumdays-backend/ai/tests/test_extract_style.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
from PIL import Image
55

66

7-
# ============================================
8-
# 1) extract_service.py 모든 함수 단독 테스트
9-
# ============================================
107
def test_l2norm_basic():
118
from ai.services.extract.extract_service import l2norm
129
x = np.array([[3.0, 4.0]])
@@ -65,19 +62,13 @@ def test_extract_style_full():
6562
assert "style_examples" in result
6663

6764

68-
# ============================================
69-
# 2) compute_style_profile_text → 실제 OpenAI 호출 테스트
70-
# ============================================
7165
def test_compute_style_profile_text():
7266
from ai.services.extract.extract_style_profile import compute_style_profile_text
7367
diaries = ["오늘은 기분이 좋았다.", "점심은 맛있었다.", "밤에는 산책을 했다.", "잤다", "일어났다"]
7468
text = compute_style_profile_text(diaries)
7569
assert len(text) >= 5
7670

7771

78-
# ============================================
79-
# 3) /extract/style API 테스트 — JSON 입력
80-
# ============================================
8172
def test_extract_style_route_json(client):
8273
diaries = [
8374
"오늘은 좋은 날이었다.",
@@ -97,10 +88,6 @@ def test_extract_style_route_json(client):
9788
assert "style_examples" in data
9889
assert "style_prompt" in data
9990

100-
101-
# ============================================
102-
# 4) /extract/style API 테스트 — diaries 부족 → 에러분기
103-
# ============================================
10491
def test_extract_style_route_too_few(client):
10592
diaries = ["하나", "둘"] # 최소 3개 → MIN_DIARY_NUM=3
10693

@@ -114,18 +101,12 @@ def test_extract_style_route_too_few(client):
114101
assert response.status_code == 400
115102

116103

117-
# ============================================
118-
# 5) /extract/style API 테스트 — form-data + 이미지 OCR 경로
119-
# ============================================
120104
def test_extract_style_route_with_images(client):
121-
# 더미 이미지를 메모리에서 생성 → Vision OCR 호출됨
122105
img = Image.new("RGB", (200, 60), color=(255, 255, 255))
123106
buf = io.BytesIO()
124107
img.save(buf, format="PNG")
125108
buf.seek(0)
126109

127-
# diaries는 1개만 넣고, OCR 결과에서 최소 2개 이상 텍스트가 나오도록 기대
128-
# (실제 Vision 결과에 따라 다르지만, 호출 자체가 성공하면 커버리지 충족됨)
129110
data = {
130111
"diaries": json.dumps(["오늘 일기를 썼다."]),
131112
}
@@ -139,6 +120,4 @@ def test_extract_style_route_with_images(client):
139120
content_type="multipart/form-data"
140121
)
141122

142-
# OCR이 실패하더라도 detect_texts_from_diaries 내부에서 포맷은 반환됨
143-
# diaries ≥ 3 이면 200, 아니면 400
144123
assert response.status_code in (200, 400)

sumdays-backend/ai/tests/test_image_service.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,12 @@
22
import os
33
import pytest
44

5-
# 실제 이미지 파일 경로 설정
6-
# ai/tests/sample.jpg, ai/tests/diary_page1.jpg, ai/tests/diary_page2.jpg 준비 필요
75
SAMPLE_IMG = os.path.join("ai", "tests", "images", "sample.jpg")
86
DIARY_IMG_1 = os.path.join("ai", "tests", "images", "diary_page1.jpg")
97
DIARY_IMG_2 = os.path.join("ai", "tests", "images", "diary_page2.jpg")
108

119

1210
def test_image_memo_extract(client):
13-
"""
14-
[통합 테스트] /image/memo (type=extract)
15-
- 실제 이미지 파일로 OCR 추출
16-
"""
1711
with open(SAMPLE_IMG, "rb") as f:
1812
data = {"type": "extract", "image": (io.BytesIO(f.read()), "sample.jpg")}
1913

@@ -32,10 +26,6 @@ def test_image_memo_extract(client):
3226

3327

3428
def test_image_memo_describe(client):
35-
"""
36-
[통합 테스트] /image/memo (type=describe)
37-
- 실제 이미지 파일로 GPT Vision 호출
38-
"""
3929
with open(SAMPLE_IMG, "rb") as f:
4030
data = {"type": "describe", "image": (io.BytesIO(f.read()), "sample.jpg")}
4131

@@ -54,9 +44,6 @@ def test_image_memo_describe(client):
5444

5545

5646
def test_image_memo_invalid_type(client):
57-
"""
58-
[에러 테스트] /image/memo invalid type
59-
"""
6047
with open(SAMPLE_IMG, "rb") as f:
6148
data = {"type": "nonsense", "image": (io.BytesIO(f.read()), "sample.jpg")}
6249

@@ -68,9 +55,6 @@ def test_image_memo_invalid_type(client):
6855

6956

7057
def test_image_memo_no_file(client):
71-
"""
72-
[에러 테스트] /image/memo without image
73-
"""
7458
data = {"type": "extract"}
7559
res = client.post("/image/memo", data=data, content_type="multipart/form-data")
7660
data = res.get_json()
@@ -80,10 +64,6 @@ def test_image_memo_no_file(client):
8064

8165

8266
def test_image_diary_multiple(client):
83-
"""
84-
[통합 테스트] /image/diary
85-
- 실제 이미지 2장 업로드, Vision + GPT refine 경로 모두 실행
86-
"""
8767
with open(DIARY_IMG_1, "rb") as f1, open(DIARY_IMG_2, "rb") as f2:
8868
files = [("image", (io.BytesIO(f1.read()), "page1.jpg")),
8969
("image", (io.BytesIO(f2.read()), "page2.jpg"))]
@@ -102,9 +82,6 @@ def test_image_diary_multiple(client):
10282

10383

10484
def test_image_diary_no_files(client):
105-
"""
106-
[에러 테스트] /image/diary without files
107-
"""
10885
res = client.post("/image/diary", data={}, content_type="multipart/form-data")
10986
data = res.get_json()
11087

sumdays-backend/ai/tests/test_merge_service.py

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,8 @@
1010
merge_rerank,
1111
)
1212

13-
# ============================================================
14-
# 0) analysis_service + OpenAI 전체 mock (라우트 400 방지)
15-
# ============================================================
1613
@pytest.fixture(autouse=True)
1714
def mock_all(monkeypatch):
18-
"""
19-
merge 라우트에 사용되는 모든 외부 요소를 mock:
20-
- analysis_service.analyze
21-
- OpenAI completions.create
22-
"""
23-
24-
# ---------------------------
25-
# 1) diary 분석 mock
26-
# ---------------------------
2715
class FakeAnalysis:
2816
def analyze(self, diary):
2917
return {
@@ -38,9 +26,6 @@ def analyze(self, diary):
3826
merge_routes.analysis_service, "analyze", FakeAnalysis().analyze
3927
)
4028

41-
# ---------------------------
42-
# 2) OpenAI completions mock
43-
# ---------------------------
4429
import ai.services.merge.merge_service as ms
4530

4631
class FakeMessage:
@@ -55,7 +40,6 @@ class FakeResponse:
5540
def __init__(self, content):
5641
self.choices = [FakeChoice(content)]
5742

58-
# merge_rerank가 반드시 텍스트를 생성할 수 있도록 한다
5943
def fake_create(*args, **kwargs):
6044
return FakeResponse("첫번째 단락입니다.\n###\n두번째 단락입니다.")
6145

@@ -64,9 +48,6 @@ def fake_create(*args, **kwargs):
6448
)
6549

6650

67-
# ============================================================
68-
# 공용 payload
69-
# ============================================================
7051
def _build_base_payload(end_flag: bool, advanced_flag: bool = False) -> dict:
7152
return {
7253
"memos": [
@@ -89,16 +70,8 @@ def _build_base_payload(end_flag: bool, advanced_flag: bool = False) -> dict:
8970
}
9071

9172

92-
# ============================================================
93-
# 1) /merge/ 라우트 테스트
94-
# ============================================================
9573

9674
def test_merge_only_streaming_route(client):
97-
"""
98-
Case A:
99-
end_flag=False AND advanced_flag=False
100-
→ merge_stream → text/plain
101-
"""
10275
payload = _build_base_payload(end_flag=False, advanced_flag=False)
10376

10477
res = client.post("/merge/", data=json.dumps(payload), content_type="application/json")
@@ -109,52 +82,7 @@ def test_merge_only_streaming_route(client):
10982
body = res.get_data(as_text=True).strip()
11083
assert body != ""
11184

112-
113-
# def test_merge_rerank_route(client):
114-
# """
115-
# Case B:
116-
# end_flag=False AND advanced_flag=True
117-
# → merge_rerank → string
118-
# """
119-
# payload = _build_base_payload(end_flag=False, advanced_flag=True)
120-
121-
# res = client.post("/merge/", data=json.dumps(payload), content_type="application/json")
122-
123-
# assert res.status_code == 200
124-
125-
# body = res.get_data(as_text=True)
126-
# assert isinstance(body, str)
127-
# assert body.strip() != ""
128-
129-
130-
# def test_merge_and_analyze_route(client):
131-
# """
132-
# Case C:
133-
# end_flag=True → merge 후 analysis → JSON
134-
# """
135-
# payload = _build_base_payload(end_flag=True, advanced_flag=True)
136-
# payload["entry_date"] = "2025-10-29"
137-
# payload["user_id"] = 123
138-
139-
# res = client.post("/merge/", data=json.dumps(payload), content_type="application/json")
140-
141-
# assert res.status_code == 200
142-
143-
# data = res.get_json()
144-
# assert "entry_date" in data
145-
# assert "user_id" in data
146-
# assert "diary" in data
147-
# assert "icon" in data
148-
# assert "ai_comment" in data
149-
# assert "analysis" in data
150-
151-
# assert data["diary"].strip() != ""
152-
153-
15485
def test_merge_bad_request_route(client):
155-
"""
156-
잘못된 요청 → KeyError → except → 400
157-
"""
15886
bad_payload = {
15987
# memos 누락
16088
"end_flag": False,
@@ -171,10 +99,6 @@ def test_merge_bad_request_route(client):
17199
assert "error" in res.get_json()
172100

173101

174-
# ============================================================
175-
# 2) helper 함수 테스트
176-
# ============================================================
177-
178102
def test_l2norm_basic_and_zero():
179103
vec = np.array([[3.0, 4.0]], dtype=np.float32)
180104
out = l2norm(vec)
@@ -219,17 +143,8 @@ def test_embed_sentences_shape_and_norm():
219143
assert np.allclose(np.linalg.norm(E, axis=-1), 1.0, atol=1e-3)
220144

221145

222-
# ============================================================
223-
# 3) merge_rerank 전체 로직 커버
224-
# ============================================================
225146

226147
def test_merge_rerank_full_flow(monkeypatch):
227-
"""
228-
merge_rerank 내부 전체 흐름 커버 테스트
229-
- 첫번째 메모 → 단락 선택됨
230-
- 두번째 메모 → blocks 없음 → skip
231-
- 세번째 메모 → 중복 단락 skip
232-
"""
233148
from ai.services.merge import merge_service as ms
234149

235150
def fake_embed(sentences):

0 commit comments

Comments
 (0)