Coverage for flask/src/rasp_water/control/webapi/test/time.py: 20%
66 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-04 12:06 +0900
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-04 12:06 +0900
1#!/usr/bin/env python3
3import datetime
4import logging
5import os
7import my_lib.time
8import my_lib.webapp.config
9import time_machine
11import flask
13blueprint = flask.Blueprint("rasp-water-test-time", __name__, url_prefix=my_lib.webapp.config.URL_PREFIX)
16# テスト用の時刻モック状態を保持
17_traveler = None
20@blueprint.route("/api/test/time/set/<timestamp>", methods=["POST"])
21def set_mock_time(timestamp):
22 """
23 テスト用時刻を設定するAPI
25 Args:
26 timestamp: Unix timestamp (秒) またはISO形式の日時文字列
28 Returns:
29 JSON: 設定された時刻情報
31 """
32 global _traveler # noqa: PLW0603
34 # DUMMY_MODE でない場合は拒否
35 if os.environ.get("DUMMY_MODE", "false") != "true":
36 return {"error": "Test API is only available in DUMMY_MODE"}, 403
38 try:
39 # タイムスタンプの解析
40 if timestamp.isdigit():
41 mock_datetime = datetime.datetime.fromtimestamp(int(timestamp), tz=my_lib.time.get_zoneinfo())
42 else:
43 # ISO形式の解析
44 mock_datetime = datetime.datetime.fromisoformat(timestamp)
45 if mock_datetime.tzinfo is None:
46 mock_datetime = mock_datetime.replace(tzinfo=my_lib.time.get_zoneinfo())
48 # 既存のtravelerを停止
49 if _traveler:
50 _traveler.stop()
52 # time_machineを使用して時刻を設定
53 _traveler = time_machine.travel(mock_datetime)
54 _traveler.start()
56 logging.info("Mock time set to: %s", mock_datetime)
58 return {
59 "success": True,
60 "mock_time": mock_datetime.isoformat(),
61 "unix_timestamp": int(mock_datetime.timestamp()),
62 }
64 except (ValueError, TypeError) as e:
65 return {"error": f"Invalid timestamp format: {e}"}, 400
68@blueprint.route("/api/test/time/advance/<int:seconds>", methods=["POST"])
69def advance_mock_time(seconds):
70 """
71 モック時刻を指定秒数進める
73 Args:
74 seconds: 進める秒数
76 Returns:
77 JSON: 更新された時刻情報
79 """
80 global _traveler # noqa: PLW0603
82 # DUMMY_MODE でない場合は拒否
83 if os.environ.get("DUMMY_MODE", "false") != "true":
84 return {"error": "Test API is only available in DUMMY_MODE"}, 403
86 if _traveler is None:
87 return {"error": "Mock time not set. Use /api/test/time/set first"}, 400
89 # 現在の時刻を取得して、新しい時刻を計算
90 current_mock_time = my_lib.time.now()
91 new_mock_time = current_mock_time + datetime.timedelta(seconds=seconds)
93 # 既存のtravelerを停止
94 _traveler.stop()
96 # 新しい時刻でtravelerを再作成
97 _traveler = time_machine.travel(new_mock_time)
98 _traveler.start()
100 # スケジューラーに現在のスケジュールを再読み込みさせる
101 try:
102 import rasp_water.control.scheduler
103 from rasp_water.control.webapi.schedule import schedule_queue
105 current_schedule = rasp_water.control.scheduler.schedule_load()
106 schedule_queue.put(current_schedule)
107 logging.info("Forced scheduler reload with current schedule")
109 # スケジューラーの内部時刻同期のため複数回リロード
110 for i in range(3):
111 schedule_queue.put(current_schedule)
113 except Exception as e:
114 logging.warning("Failed to force scheduler reload: %s", e)
116 current_time = my_lib.time.now()
117 logging.info("Mock time advanced to: %s", current_time)
119 return {
120 "success": True,
121 "mock_time": current_time.isoformat(),
122 "unix_timestamp": int(current_time.timestamp()),
123 "advanced_seconds": seconds,
124 }
127@blueprint.route("/api/test/time/reset", methods=["POST"])
128def reset_mock_time():
129 """
130 モック時刻をリセットして実際の時刻に戻す
132 Returns:
133 JSON: リセット結果
135 """
136 global _traveler # noqa: PLW0603
138 # DUMMY_MODE でない場合は拒否
139 if os.environ.get("DUMMY_MODE", "false") != "true":
140 return {"error": "Test API is only available in DUMMY_MODE"}, 403
142 if _traveler:
143 _traveler.stop()
144 _traveler = None
146 logging.info("Mock time reset to real time")
148 return {"success": True, "real_time": my_lib.time.now().isoformat()}
151@blueprint.route("/api/test/time/current", methods=["GET"])
152def get_current_time():
153 """
154 現在の時刻(モック時刻または実時刻)を取得
156 Returns:
157 JSON: 現在時刻情報
159 """
160 # DUMMY_MODE でない場合は拒否
161 if os.environ.get("DUMMY_MODE", "false") != "true":
162 return {"error": "Test API is only available in DUMMY_MODE"}, 403
164 current_time = my_lib.time.now()
166 return {
167 "current_time": current_time.isoformat(),
168 "unix_timestamp": int(current_time.timestamp()),
169 "is_mocked": _traveler is not None,
170 "mock_time": current_time.isoformat() if _traveler else None,
171 }