""" E2E 테스트 — 컨테이너에서 직접 실행. 사용: docker exec hackathon-vote python3 /tmp/e2e.py """ import os import sys import tempfile # 테스트용 격리 DB TEST_DB = tempfile.mktemp(suffix=".db") os.environ["VOTE_DB"] = TEST_DB os.environ["ADMIN_TOKEN"] = "test" sys.path.insert(0, "/app") # import after env set from app import ( # noqa: E402 get_conn, get_participants, get_teams, compute_winners, save_tie_break, clear_tie_break, set_voting_open, is_voting_open, set_title, get_titles, archive_results, ) # ---- helpers ---- def reset_db(): """테스트 DB 초기화.""" if os.path.exists(TEST_DB): os.unlink(TEST_DB) conn = get_conn() conn.close() def insert_vote(name, emp_id, team, fun, polish, utility): conn = get_conn() conn.execute( "INSERT INTO votes (voter_name, employee_id, voter_team, " "fun_team, polish_team, utility_team, created_at) " "VALUES (?, ?, ?, ?, ?, ?, datetime('now'))", (name, emp_id, team, fun, polish, utility), ) conn.commit() conn.close() def vote_count(): conn = get_conn() n = conn.execute("SELECT COUNT(*) FROM votes").fetchone()[0] conn.close() return n # ---- tests ---- PASS = "✅" FAIL = "❌" results = [] def test(name, fn): try: fn() results.append((PASS, name)) print(f"{PASS} {name}") except AssertionError as e: results.append((FAIL, f"{name}: {e}")) print(f"{FAIL} {name}: {e}") except Exception as e: results.append((FAIL, f"{name}: UNEXPECTED {type(e).__name__}: {e}")) print(f"{FAIL} {name}: UNEXPECTED {type(e).__name__}: {e}") def t_roster_load(): parts = get_participants() teams = get_teams() assert len(parts) == 34, f"기대 34명, 실제 {len(parts)}" assert len(teams) == 7, f"기대 7팀, 실제 {len(teams)}" assert "한지승" in parts, "한지승 명단에 없음" assert "김태현" not in parts, "김태현 진행요원이 명단에 있음" def t_voting_toggle(): reset_db() assert is_voting_open() is True, "기본 open" set_voting_open(False) assert is_voting_open() is False, "마감 적용 안 됨" set_voting_open(True) assert is_voting_open() is True, "재개 적용 안 됨" def t_simple_winners_no_priority_collision(): """팀1=fun, 팀2=polish, 팀3=utility 각각 명확한 1위. priority 충돌 없음.""" reset_db() # 팀1에 5표 (fun), 팀2에 5표 (polish), 팀3에 5표 (utility) for i in range(5): insert_vote(f"V{i}", f"E{i}", "팀7", "팀1", "팀2", "팀3") winners, _ = compute_winners() assert winners["fun_team"]["status"] == "ok" assert winners["fun_team"]["team"] == "팀1" assert winners["polish_team"]["team"] == "팀2" assert winners["utility_team"]["team"] == "팀3" def t_priority_one_team_all_first(): """팀1이 모든 분야 1위 → 팀1은 utility만, polish/fun은 다음 팀.""" reset_db() # 팀1에 모든 분야 5표, 팀2에 polish/fun 3표, 팀3에 fun 2표 for i in range(5): insert_vote(f"V{i}", f"E{i}", "팀7", "팀1", "팀1", "팀1") for i in range(5, 8): insert_vote(f"V{i}", f"E{i}", "팀7", "팀2", "팀2", "팀1") for i in range(8, 10): insert_vote(f"V{i}", f"E{i}", "팀7", "팀3", "팀3", "팀2") winners, _ = compute_winners() # 팀1이 utility 1위 → 팀1 수상 assert winners["utility_team"]["team"] == "팀1", winners["utility_team"] # 팀1 제외 → polish 1위 = 팀2 (3+5표=8 wait, 팀1=5, 팀2=3+...) 다시: # polish 분포: 팀1=5, 팀2=3, 팀3=2 → 팀1 제외 후 팀2 assert winners["polish_team"]["team"] == "팀2", winners["polish_team"] # fun: 팀1=5, 팀2=3, 팀3=2 → 팀1, 팀2 제외 후 팀3 assert winners["fun_team"]["team"] == "팀3", winners["fun_team"] def t_tie_pending(): """팀1, 팀2 utility 동률 → status='tie'.""" reset_db() for i in range(3): insert_vote(f"V{i}", f"E{i}", "팀7", "팀5", "팀6", "팀1") for i in range(3, 6): insert_vote(f"V{i}", f"E{i}", "팀7", "팀5", "팀6", "팀2") winners, _ = compute_winners() assert winners["utility_team"]["status"] == "tie", winners["utility_team"] assert set(winners["utility_team"]["tied"]) == {"팀1", "팀2"} def t_tie_break_random(): """동률 → save_tie_break으로 결정 → status='ok'.""" reset_db() for i in range(3): insert_vote(f"V{i}", f"E{i}", "팀7", "팀5", "팀6", "팀1") for i in range(3, 6): insert_vote(f"V{i}", f"E{i}", "팀7", "팀5", "팀6", "팀2") save_tie_break("utility_team", "팀1", "random") winners, _ = compute_winners() assert winners["utility_team"]["status"] == "ok" assert winners["utility_team"]["team"] == "팀1" assert winners["utility_team"]["method"] == "random" clear_tie_break("utility_team") def t_unique_voter(): """같은 voter_name 두 번 INSERT 차단.""" reset_db() insert_vote("홍길동", "E1", "팀1", "팀2", "팀3", "팀4") try: insert_vote("홍길동", "E2", "팀1", "팀5", "팀6", "팀7") raise AssertionError("중복 INSERT 통과 (UNIQUE 위반 예상)") except Exception as e: # IntegrityError 기대 assert "UNIQUE" in str(e) or "unique" in str(e).lower() def t_titles_persist(): reset_db() set_title("팀1", "Slack 자동 분류기") titles = get_titles() assert titles["팀1"] == "Slack 자동 분류기" def t_archive_writes_file(): """ceremony archive — 모든 winners ok 시 파일 생성.""" reset_db() # 명확한 1위 만들기 for i in range(5): insert_vote(f"V{i}", f"E{i}", "팀7", "팀1", "팀2", "팀3") archive_dir = os.path.dirname(TEST_DB) or "." before = set(f for f in os.listdir(archive_dir) if f.startswith("results_")) path = archive_results() assert path is not None, "archive 결과 None" assert os.path.exists(path) after = set(f for f in os.listdir(archive_dir) if f.startswith("results_")) assert len(after) > len(before), "archive 파일 생성 안 됨" # 정리 os.unlink(path) def t_archive_skips_when_pending(): """동률 미해결 시 archive 안 함.""" reset_db() for i in range(3): insert_vote(f"V{i}", f"E{i}", "팀7", "팀5", "팀6", "팀1") for i in range(3, 6): insert_vote(f"V{i}", f"E{i}", "팀7", "팀5", "팀6", "팀2") path = archive_results() assert path is None, f"동률 미해결인데 archive 됨: {path}" # ---- run ---- if __name__ == "__main__": print(f"# E2E 테스트 (DB={TEST_DB})\n") test("roster.json 로드 (34명, 7팀)", t_roster_load) test("투표 마감 토글", t_voting_toggle) test("기본 winner (priority 충돌 없음)", t_simple_winners_no_priority_collision) test("priority 1팀 모든 분야 1위", t_priority_one_team_all_first) test("동률 status='tie'", t_tie_pending) test("tie_break 적용 시 status='ok'", t_tie_break_random) test("voter_name UNIQUE 강제", t_unique_voter) test("팀 제목 영속", t_titles_persist) test("archive 파일 생성", t_archive_writes_file) test("archive 동률 미해결 시 skip", t_archive_skips_when_pending) fails = sum(1 for r, _ in results if r == FAIL) print(f"\n# 합계: {len(results)} 중 통과 {len(results) - fails}, 실패 {fails}") sys.exit(0 if fails == 0 else 1)