n8n 連携: 受け口を呼ぶクライアントと設定の保持を追加
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -170,5 +170,111 @@ class CollectAndMarkTest(unittest.TestCase):
|
||||
self.assertEqual(sorted(o["kind"] for o in b3["overlaps"]), ["block", "row"]) # 進行中の行 (終了 = 今) とも重なる
|
||||
|
||||
|
||||
class FakeN8n:
|
||||
"""wt-import の受け口を真似る偽のサーバー。受け取った要求を記録する。"""
|
||||
|
||||
TOKEN = "test-token"
|
||||
|
||||
def __init__(self):
|
||||
self.requests = []
|
||||
fake = self
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
def reply(self, status, payload):
|
||||
body = json.dumps(payload).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
fake.requests.append(("GET", self.path, self.headers.get("X-WT-Token")))
|
||||
if self.headers.get("X-WT-Token") != FakeN8n.TOKEN:
|
||||
return self.reply(403, {"message": "Authorization data is wrong!"})
|
||||
if "from=2099" in self.path:
|
||||
return self.reply(200, {"ok": False, "error": "from が to より後です"})
|
||||
self.reply(200, {"ok": True, "rows": [{"id": 9, "source": "chktime:s1:1", "start_time": None}], "deletedSources": []})
|
||||
|
||||
def do_POST(self):
|
||||
length = int(self.headers.get("Content-Length") or 0)
|
||||
body = json.loads(self.rfile.read(length))
|
||||
fake.requests.append(("POST", self.path, body))
|
||||
if self.headers.get("X-WT-Token") != FakeN8n.TOKEN:
|
||||
return self.reply(403, {})
|
||||
inserted = [{"source": r["source"], "id": 100 + i} for i, r in enumerate(body["rows"])]
|
||||
self.reply(200, {"ok": True, "inserted": inserted, "skipped": [], "errors": []})
|
||||
|
||||
self.server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||
self.url = f"http://127.0.0.1:{self.server.server_address[1]}"
|
||||
threading.Thread(target=self.server.serve_forever, daemon=True).start()
|
||||
|
||||
def close(self):
|
||||
self.server.shutdown()
|
||||
self.server.server_close()
|
||||
|
||||
|
||||
class N8nClientTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.fake = FakeN8n()
|
||||
|
||||
def tearDown(self):
|
||||
self.fake.close()
|
||||
|
||||
def test_rows_sends_token_and_range(self):
|
||||
client = chktime.N8n(self.fake.url, FakeN8n.TOKEN)
|
||||
data = client.rows(date(2026, 1, 1), date(2026, 1, 7))
|
||||
self.assertEqual(data["rows"][0]["id"], 9)
|
||||
method, path, token = self.fake.requests[-1]
|
||||
self.assertEqual(path, "/webhook/wt-import-rows?from=2026-01-01&to=2026-01-07")
|
||||
self.assertEqual(token, FakeN8n.TOKEN)
|
||||
|
||||
def test_wrong_token_is_403_message(self):
|
||||
client = chktime.N8n(self.fake.url, "wrong")
|
||||
with self.assertRaisesRegex(chktime.N8nError, "合言葉が違います"):
|
||||
client.rows(date(2026, 1, 1), date(2026, 1, 1))
|
||||
|
||||
def test_error_payload_raises(self):
|
||||
client = chktime.N8n(self.fake.url, FakeN8n.TOKEN)
|
||||
with self.assertRaisesRegex(chktime.N8nError, "from が to より後です"):
|
||||
client.rows(date(2099, 1, 2), date(2099, 1, 1))
|
||||
|
||||
def test_unreachable(self):
|
||||
client = chktime.N8n("http://127.0.0.1:9", FakeN8n.TOKEN)
|
||||
with self.assertRaisesRegex(chktime.N8nError, "接続できません"):
|
||||
client.rows(date(2026, 1, 1), date(2026, 1, 1))
|
||||
|
||||
def test_missing_settings(self):
|
||||
with self.assertRaisesRegex(chktime.N8nError, "URL が未設定"):
|
||||
chktime.N8n("", "x").rows(date(2026, 1, 1), date(2026, 1, 1))
|
||||
with self.assertRaisesRegex(chktime.N8nError, "合言葉が未設定"):
|
||||
chktime.N8n(self.fake.url, "").rows(date(2026, 1, 1), date(2026, 1, 1))
|
||||
|
||||
|
||||
class N8nHolderTest(unittest.TestCase):
|
||||
def test_public_hides_token_and_update_keeps_it(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "n8n.json"
|
||||
holder = chktime.N8nHolder(path)
|
||||
self.assertEqual(holder.settings(), (30, 5))
|
||||
pub = holder.update({"url": "https://n8n.example/", "token": "secret", "gapMinutes": 20, "minMinutes": 3})
|
||||
self.assertTrue(pub["hasToken"])
|
||||
self.assertNotIn("secret", json.dumps(pub))
|
||||
self.assertEqual(holder.settings(), (20, 3))
|
||||
holder.update({"token": ""}) # 空なら既存を残す
|
||||
self.assertEqual(json.loads(path.read_text(encoding="utf-8"))["token"], "secret")
|
||||
self.assertEqual(holder.client.url, "https://n8n.example")
|
||||
|
||||
def test_rejects_bad_minutes(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
holder = chktime.N8nHolder(Path(tmp) / "n8n.json")
|
||||
for patch in ({"gapMinutes": 0}, {"gapMinutes": 241}, {"minMinutes": -1}, {"gapMinutes": "abc"}):
|
||||
with self.assertRaises(chktime.N8nError):
|
||||
holder.update(patch)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user