1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
#!/usr/bin/env python3
"""Tests for keep-bridge's pure shaping helpers + its failure degradation.
The gkeepapi auth/fetch path is the IO boundary and is exercised live once the
token is configured; here we test the JSON-shaping logic (the round-trip
contract the elisp side reads) with duck-typed stand-ins, plus a subprocess
smoke test that the script degrades with a reason token rather than crashing.
Run: python3 -m unittest test_keep_bridge (from scripts/google-keep/)
"""
import importlib.util
import os
import subprocess
import sys
import unittest
from datetime import datetime, timezone, timedelta
_HERE = os.path.dirname(os.path.abspath(__file__))
_BRIDGE = os.path.join(_HERE, "keep-bridge.py")
_spec = importlib.util.spec_from_file_location("keep_bridge", _BRIDGE)
assert _spec and _spec.loader
kb = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(kb)
# --- duck-typed stand-ins for a gkeepapi note ---------------------------------
class FakeLabel:
def __init__(self, name):
self.name = name
class FakeLabels:
def __init__(self, names):
self._labels = [FakeLabel(n) for n in names]
def all(self):
return self._labels
class FakeTimestamps:
def __init__(self, updated):
self.updated = updated
class FakeColor:
def __init__(self, value):
self.value = value
class FakeNote:
def __init__(self, id="n1", title: object = "T", text: object = "B", labels=(),
pinned=False, archived=False, color: object = "WHITE", updated=None):
self.id = id
self.title = title
self.text = text
self.labels = FakeLabels(labels)
self.pinned = pinned
self.archived = archived
self.color = color
self.timestamps = FakeTimestamps(updated)
class TestIso8601Utc(unittest.TestCase):
def test_normal_naive_datetime_treated_as_utc(self):
self.assertEqual(kb.iso8601_utc(datetime(2026, 6, 25, 4, 12, 0)),
"2026-06-25T04:12:00Z")
def test_normal_aware_datetime_converted_to_utc(self):
est = timezone(timedelta(hours=-5))
self.assertEqual(kb.iso8601_utc(datetime(2026, 6, 24, 23, 12, 0, tzinfo=est)),
"2026-06-25T04:12:00Z")
def test_boundary_none_returns_none(self):
self.assertIsNone(kb.iso8601_utc(None))
class TestColorName(unittest.TestCase):
def test_normal_enum_with_value(self):
self.assertEqual(kb.color_name(FakeColor("RED")), "RED")
def test_normal_plain_string(self):
self.assertEqual(kb.color_name("WHITE"), "WHITE")
def test_boundary_name_only_object(self):
class C:
name = "BLUE"
self.assertEqual(kb.color_name(C()), "BLUE")
class TestNoteToDict(unittest.TestCase):
def test_normal_full_note(self):
note = FakeNote(id="abc", title="Groceries", text="milk\neggs",
labels=("shopping", "home"), pinned=True, archived=False,
color=FakeColor("YELLOW"),
updated=datetime(2026, 6, 25, 4, 0, 0, tzinfo=timezone.utc))
self.assertEqual(kb.note_to_dict(note), {
"id": "abc",
"title": "Groceries",
"text": "milk\neggs",
"labels": ["shopping", "home"],
"pinned": True,
"archived": False,
"color": "YELLOW",
"updated": "2026-06-25T04:00:00Z",
})
def test_boundary_empty_title_and_no_labels(self):
note = FakeNote(title="", labels=(), color="WHITE",
updated=datetime(2026, 1, 1, tzinfo=timezone.utc))
d = kb.note_to_dict(note)
self.assertEqual(d["title"], "")
self.assertEqual(d["labels"], [])
def test_boundary_none_title_text_coerced_to_empty(self):
note = FakeNote(title=None, text=None, color="WHITE",
updated=datetime(2026, 1, 1, tzinfo=timezone.utc))
d = kb.note_to_dict(note)
self.assertEqual(d["title"], "")
self.assertEqual(d["text"], "")
class TestNotesToJson(unittest.TestCase):
def test_normal_array_of_notes(self):
import json
notes = [FakeNote(id="a", updated=datetime(2026, 1, 1, tzinfo=timezone.utc)),
FakeNote(id="b", updated=datetime(2026, 1, 2, tzinfo=timezone.utc))]
parsed = json.loads(kb.notes_to_json(notes))
self.assertEqual([n["id"] for n in parsed], ["a", "b"])
def test_boundary_empty_keep_is_empty_array(self):
self.assertEqual(kb.notes_to_json([]), "[]")
class TestDegradation(unittest.TestCase):
def test_error_no_env_exits_nonzero_with_reason_token(self):
# With no KEEP_EMAIL/KEEP_MASTER_TOKEN the script must exit non-zero
# with a single reason token, never crash. The exact token depends on
# whether gkeepapi is installed in this environment.
env = {k: v for k, v in os.environ.items()
if k not in ("KEEP_EMAIL", "KEEP_MASTER_TOKEN")}
proc = subprocess.run([sys.executable, _BRIDGE], env=env,
capture_output=True, text=True)
self.assertNotEqual(proc.returncode, 0)
self.assertIn(proc.stderr.strip(), ("no-gkeepapi", "no-token"))
self.assertEqual(proc.stdout, "")
if __name__ == "__main__":
unittest.main()
|