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
|
#!/usr/bin/env python3
# Author: cirakg & claude ai
# Summary: Generate the settings-spike test strings: one .txt per string and an index.html with copy buttons.
"""Generate the test strings for the M1 settings spike (docs/SETTINGS_SPIKE.md).
Length strings look like
N=1000|abcdef...P0050...P0100...xyzEND
"N=<length>|" says how long the string should be, a marker "P<position>"
sits at every 50th character, and "END" closes it. If the phone app cuts a
string, the watch shows a wrong length and no "END"; the last marker it shows
tells roughly where the cut happened. Only letters, digits, "=", "|" are
used, so the length tests don't also test character handling.
special.txt holds the characters that may be rejected (& < > \\ " { } %),
non-ASCII text, a fake otpauth URI and a JSON fragment.
The phone refused to save special.txt (spike run 2026-10-21), so the c-*.txt
strings split it into one character group each, and aegis-json.txt is a
complete fake unencrypted Aegis export: together they show which characters
block saving and whether an Aegis file can be pasted at all.
Usage: python3 make_test_strings.py (writes into its own folder)
"""
from __future__ import annotations
import html
from pathlib import Path
LENGTHS = (100, 255, 256, 500, 800, 801, 1000, 2000, 4000, 8000)
SPECIAL = (
'special: & < > \\ " { } % | non-ASCII: äöü ß € 😀 | '
"otpauth://totp/Fake:alice?secret=JBSWY3DPEHPK3PXP&issuer=Fake&digits=6 | "
'{"name":"Fake","path":"a\\/b"} END'
)
# Test C, split: (file name, label, string). Each ends with " END".
SPLIT = (
("c-1-percent", "C1: percent", "C1 % 100% END"),
("c-2-angle", "C2: angle brackets", "C2 < > <b> END"),
("c-3-backslash", "C3: backslash", "C3 \\ a\\/b END"),
("c-4-quote", "C4: double quote", 'C4 " "x" END'),
("c-5-braces", "C5: braces", "C5 { } {x} END"),
("c-6-latin", "C6: accented letters and euro sign", "C6 äöü ß € END"),
("c-7-emoji", "C7: emoji", "C7 😀 END"),
("c-8-json", "C8: JSON fragment", 'C8 {"name":"Fake","n":1} END'),
)
AEGIS_JSON = (
'{"version":1,"header":{"slots":null,"params":null},"db":{"version":3,"entries":['
'{"type":"totp","uuid":"00000000-0000-4000-8000-000000000001","name":"alice@example.com",'
'"issuer":"TestOne","note":"","icon":null,"info":{"secret":"JBSWY3DPEHPK3PXP",'
'"algo":"SHA1","digits":6,"period":30}}]}}'
)
HEADER = (
"# Author: cirakg & claude ai\n# Summary: {summary}\n"
"# Copy ONLY the last line (the test string) into a settings field.\n\n"
)
LETTERS = "abcdefghijklmnopqrstuvwxyz"
def marker_string(length: int) -> str:
"""A string of exactly `length` characters with position markers."""
text = f"N={length}|"
i = 0
while len(text) < length - 3:
pos = len(text)
if pos % 50 == 0 and pos + 5 <= length - 3:
text += f"P{pos:04d}"
else:
text += LETTERS[i % len(LETTERS)]
i += 1
text += "END"
assert len(text) == length, (length, len(text))
return text
def main() -> None:
here = Path(__file__).resolve().parent
entries = [(f"len-{n:04d}", f"{n} characters", marker_string(n)) for n in LENGTHS]
entries.append(("special", "special characters", SPECIAL))
entries.extend(SPLIT)
entries.append(("aegis-json", "fake unencrypted Aegis export", AEGIS_JSON))
for name, label, value in entries:
(here / f"{name}.txt").write_text(HEADER.format(summary=f"Settings spike test string: {label}.") + value + "\n")
cards = "\n".join(
f"<section><h2>{html.escape(label)}</h2>"
f'<textarea id="{name}" readonly rows="3">{html.escape(value)}</textarea>'
f"<button onclick=\"copyText('{name}')\">Copy</button></section>"
for name, label, value in entries
)
page = f"""<!doctype html>
<!-- Author: cirakg & claude ai -->
<!-- Summary: Settings spike test strings with copy buttons; open on the phone, copy, paste into a settings field. -->
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Settings spike test strings</title>
<style>body{{font-family:sans-serif;margin:16px}}textarea{{width:100%;font-family:monospace}}
button{{font-size:1.1em;padding:6px 16px;margin:4px 0 16px}}</style></head>
<body><h1>Settings spike test strings</h1>
<p>Tap Copy, then paste into one of the Text fields in the app settings. Expected lengths are in each heading.</p>
{cards}
<script>
function copyText(id) {{
const area = document.getElementById(id);
area.select();
const done = () => alert("Copied " + area.value.length + " characters");
if (navigator.clipboard && window.isSecureContext) {{
navigator.clipboard.writeText(area.value).then(done, () => {{ document.execCommand("copy"); done(); }});
}} else {{ document.execCommand("copy"); done(); }}
}}
</script></body></html>
"""
(here / "index.html").write_text(page)
print(f"wrote {len(entries)} test strings and index.html into {here}")
if __name__ == "__main__":
main()
|