A Python port of the Invisible Internet Project (I2P)
1"""Tests for net.i2p.util Tier 2 — i18n & enhanced crypto.
2
3TDD — tests for Translate/TranslateReader and FortunaRandomSource.
4"""
5
6from __future__ import annotations
7
8import os
9import tempfile
10
11import pytest
12
13from i2p_util.translate import Translate, TranslateReader
14from i2p_util.fortuna import FortunaRandomSource
15
16
17# ---------------------------------------------------------------------------
18# TranslateReader
19# ---------------------------------------------------------------------------
20
21
22class TestTranslateReader:
23 """Load .properties translation files."""
24
25 def test_load_properties(self):
26 with tempfile.TemporaryDirectory() as td:
27 path = os.path.join(td, "messages_en.properties")
28 with open(path, "w") as f:
29 f.write("greeting=Hello\n")
30 f.write("farewell=Goodbye\n")
31 reader = TranslateReader()
32 props = reader.load(path)
33 assert props["greeting"] == "Hello"
34 assert props["farewell"] == "Goodbye"
35
36 def test_skip_comments(self):
37 with tempfile.TemporaryDirectory() as td:
38 path = os.path.join(td, "test.properties")
39 with open(path, "w") as f:
40 f.write("# comment\n")
41 f.write("! another comment\n")
42 f.write("key=value\n")
43 reader = TranslateReader()
44 props = reader.load(path)
45 assert len(props) == 1
46 assert props["key"] == "value"
47
48 def test_skip_blank_lines(self):
49 with tempfile.TemporaryDirectory() as td:
50 path = os.path.join(td, "test.properties")
51 with open(path, "w") as f:
52 f.write("a=1\n\n\nb=2\n")
53 reader = TranslateReader()
54 props = reader.load(path)
55 assert len(props) == 2
56
57 def test_value_with_equals(self):
58 with tempfile.TemporaryDirectory() as td:
59 path = os.path.join(td, "test.properties")
60 with open(path, "w") as f:
61 f.write("url=http://example.com?a=1&b=2\n")
62 reader = TranslateReader()
63 props = reader.load(path)
64 assert props["url"] == "http://example.com?a=1&b=2"
65
66 def test_missing_file_returns_empty(self):
67 reader = TranslateReader()
68 props = reader.load("/nonexistent/file.properties")
69 assert props == {}
70
71 def test_colon_separator(self):
72 """Java .properties also supports : as separator."""
73 with tempfile.TemporaryDirectory() as td:
74 path = os.path.join(td, "test.properties")
75 with open(path, "w") as f:
76 f.write("key:value\n")
77 reader = TranslateReader()
78 props = reader.load(path)
79 assert props["key"] == "value"
80
81
82# ---------------------------------------------------------------------------
83# Translate
84# ---------------------------------------------------------------------------
85
86
87class TestTranslate:
88 """Locale-specific message lookup."""
89
90 def test_get_string(self):
91 t = Translate()
92 t.add_strings({"hello": "Hello, World!"})
93 assert t.get_string("hello") == "Hello, World!"
94
95 def test_get_string_missing_returns_key(self):
96 t = Translate()
97 assert t.get_string("missing_key") == "missing_key"
98
99 def test_get_string_with_params(self):
100 t = Translate()
101 t.add_strings({"greet": "Hello, {0}!"})
102 assert t.get_string("greet", "Alice") == "Hello, Alice!"
103
104 def test_set_locale(self):
105 t = Translate()
106 t.set_locale("fr")
107 assert t.get_locale() == "fr"
108
109 def test_default_locale_en(self):
110 t = Translate()
111 assert t.get_locale() == "en"
112
113 def test_load_from_directory(self):
114 with tempfile.TemporaryDirectory() as td:
115 path = os.path.join(td, "messages_en.properties")
116 with open(path, "w") as f:
117 f.write("test_key=test_value\n")
118 t = Translate()
119 t.load_bundle(td, "messages")
120 assert t.get_string("test_key") == "test_value"
121
122
123# ---------------------------------------------------------------------------
124# FortunaRandomSource
125# ---------------------------------------------------------------------------
126
127
128class TestFortunaRandomSource:
129 """Fortuna-based CSPRNG."""
130
131 def test_next_bytes(self):
132 rng = FortunaRandomSource()
133 data = rng.next_bytes(32)
134 assert len(data) == 32
135
136 def test_next_bytes_different(self):
137 rng = FortunaRandomSource()
138 a = rng.next_bytes(16)
139 b = rng.next_bytes(16)
140 assert a != b
141
142 def test_next_int_in_range(self):
143 rng = FortunaRandomSource()
144 for _ in range(100):
145 val = rng.next_int(10)
146 assert 0 <= val < 10
147
148 def test_seed_affects_output(self):
149 """Seeding with different values should produce different sequences."""
150 rng1 = FortunaRandomSource(seed=b"seed_a")
151 rng2 = FortunaRandomSource(seed=b"seed_b")
152 assert rng1.next_bytes(16) != rng2.next_bytes(16)
153
154 def test_next_boolean(self):
155 rng = FortunaRandomSource()
156 results = {rng.next_boolean() for _ in range(100)}
157 assert True in results
158 assert False in results