tonights-tale / test_flow.py
Zhenzewu's picture
Upload folder using huggingface_hub
2a5d3f5 verified
Raw
History Blame Contribute Delete
3.16 kB
"""Mock-mode end-to-end test of the branching state machine, driving the same
handler functions Gradio wires to the UI. Run: STORY_MOCK=1 python test_flow.py
"""
import os
assert os.environ.get("STORY_MOCK") == "1", "run with STORY_MOCK=1"
import app # noqa: E402
def unpack(result):
state = result[0]
return state
def current(state):
return state["nodes"][state["path"]]
def test_full_flow():
# Start: opening node with title + choice
state = unpack(
app.start_story(
"Mia", "3-6", "en",
"Hop", "little rabbit", "curious", "a small white rabbit",
"Sage", "old tortoise", "wise", "an old green tortoise",
"Didn't want to share toys today.",
)
)
node = current(state)
assert state["path"] == ""
assert node["title"], "opening must have a title"
assert node["choice"], "opening must have a choice"
assert state["images"].get("") is not None, "opening must have an illustration"
# Choose B twice -> mock yields gentle_setback at depth 2 (MOCK-B path)
state = unpack(app.choose(state, "B"))
assert state["path"] == "B"
assert current(state)["choice"], "depth 1 must still have a choice"
state = unpack(app.choose(state, "B"))
assert state["path"] == "BB"
node = current(state)
assert node["isEnding"] and node["endingTone"] == "gentle_setback", f"expected setback, got {node['endingTone']}"
# Rewind to the choice point: path back to 'B', option B disabled
state = unpack(app.back_to_choice(state))
assert state["path"] == "B"
assert state["disabled_option"] == "B"
# Replay with A -> sibling BB is a setback => replay flag forces warm
state = unpack(app.choose(state, "A"))
node = current(state)
assert state["path"] == "BA"
assert node["isEnding"] and node["endingTone"] == "warm", f"replay must end warm, got {node['endingTone']}"
assert node["moral"], "warm ending must carry a moral"
# Branch reuse: returning to an explored path must not regenerate
nodes_before = dict(state["nodes"])
state = unpack(app.back_to_choice(state))
state = unpack(app.choose(state, "A")) # BA again β€” cached
assert state["nodes"] is not nodes_before or len(state["nodes"]) == len(nodes_before)
assert state["path"] == "BA"
# Story markdown sanity
md = app._story_markdown(state)
assert "πŸŒ™" in md or len(md) > 50
print("βœ… full branching flow passed:", sorted(state["nodes"].keys()))
def test_render_shapes():
state = unpack(
app.start_story(
"Mia", "3-6", "de", # German: TTS must degrade gracefully (None)
"Hop", "rabbit", "curious", "white rabbit",
"", "", "", "",
"A small quarrel happened.",
)
)
rendered = app._render(state)
assert len(rendered) == 12, f"render tuple arity changed: {len(rendered)}"
audio = rendered[5]
assert audio is None or isinstance(audio, tuple)
print("βœ… render shapes ok (German TTS degraded gracefully)")
if __name__ == "__main__":
test_full_flow()
test_render_shapes()
print("πŸŽ‰ mock e2e: PASS")