From 70f326eb20b84e2efbfb2e4dd1dea686da4ff6c8 Mon Sep 17 00:00:00 2001
From: th-kim0823
Date: Mon, 27 Apr 2026 19:45:52 +0900
Subject: [PATCH] =?UTF-8?q?feat:=20stage=20=ED=97=AC=ED=8D=BC=20(get=5Fsta?=
=?UTF-8?q?ge,=20set=5Fstage,=20can=5Faccept=5Fvotes)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-Authored-By: Claude Sonnet 4.6
---
app.py | 23 +++++++++++++++++++++++
tests/e2e.py | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+)
diff --git a/app.py b/app.py
index 78dc0f9..4c5a7f1 100644
--- a/app.py
+++ b/app.py
@@ -111,6 +111,29 @@ def set_voting_open(flag):
update_data(_fn)
+VALID_STAGES = ("intro", "topics", "vote")
+
+
+def get_stage():
+ return load_data().get("settings", {}).get("current_stage", "intro")
+
+
+def set_stage(stage):
+ if stage not in VALID_STAGES:
+ raise ValueError(f"invalid stage: {stage!r}")
+
+ def _fn(data):
+ data["settings"]["current_stage"] = stage
+ if stage == "vote":
+ data["settings"]["voting_open"] = True
+ update_data(_fn)
+
+
+def can_accept_votes(data):
+ s = data.get("settings", {})
+ return s.get("current_stage") == "vote" and s.get("voting_open", False)
+
+
def get_tie_breaks():
return load_data().get("tie_breaks", {})
diff --git a/tests/e2e.py b/tests/e2e.py
index 2d5019e..adb84b4 100644
--- a/tests/e2e.py
+++ b/tests/e2e.py
@@ -225,6 +225,39 @@ def t_load_data_backfills_nested_settings():
assert fresh["topics"] == {"categories": []}
+def t_stage_helpers():
+ from app import get_stage, set_stage, can_accept_votes, load_data, save_data, _empty_state
+ save_data(_empty_state())
+
+ assert get_stage() == "intro"
+ assert can_accept_votes(load_data()) is False # intro → False
+
+ set_stage("vote")
+ d = load_data()
+ assert d["settings"]["current_stage"] == "vote"
+ assert d["settings"]["voting_open"] is True
+ assert can_accept_votes(d) is True
+
+ set_stage("topics")
+ d = load_data()
+ assert d["settings"]["current_stage"] == "topics"
+ assert d["settings"]["voting_open"] is True # 떠나도 voting_open 유지
+ assert can_accept_votes(d) is False # stage 다르면 False
+
+ # 명시적 마감 후 vote 다시 들어오면 voting_open 자동 True
+ d["settings"]["voting_open"] = False
+ save_data(d)
+ set_stage("vote")
+ assert can_accept_votes(load_data()) is True
+
+ # invalid stage → ValueError
+ try:
+ set_stage("invalid_stage")
+ raise AssertionError("expected ValueError")
+ except ValueError:
+ pass
+
+
if __name__ == "__main__":
print(f"# E2E (data={TEST_DATA})\n")
test("hackathon.json 로드 (34명, 7팀)", t_load)
@@ -241,6 +274,7 @@ if __name__ == "__main__":
test("clear_votes", t_vote_count)
test("empty_state 신규 키", t_empty_state_has_topics_and_stage)
test("load_data nested 키 backfill", t_load_data_backfills_nested_settings)
+ test("stage 헬퍼", t_stage_helpers)
fails = sum(1 for r, _ in results if r == FAIL)
print(f"\n# {len(results)} 중 통과 {len(results) - fails}, 실패 {fails}")