rapydscript-ns 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +19 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_GAPS.md +420 -0
  8. package/README.md +153 -29
  9. package/TODO.md +16 -118
  10. package/add-toc-to-readme +2 -2
  11. package/bin/export +75 -75
  12. package/bin/rapydscript +70 -70
  13. package/bin/web-repl-export +102 -102
  14. package/build +2 -2
  15. package/language-service/index.js +237 -8
  16. package/memory/project_string_impl.md +43 -0
  17. package/package.json +1 -1
  18. package/publish.py +37 -37
  19. package/release/baselib-plain-pretty.js +248 -38
  20. package/release/baselib-plain-ugly.js +8 -8
  21. package/release/compiler.js +778 -277
  22. package/release/signatures.json +30 -30
  23. package/session.vim +4 -4
  24. package/setup.cfg +2 -2
  25. package/src/ast.pyj +4 -1
  26. package/src/baselib-builtins.pyj +56 -2
  27. package/src/baselib-containers.pyj +2 -0
  28. package/src/baselib-errors.pyj +7 -3
  29. package/src/baselib-internal.pyj +51 -6
  30. package/src/baselib-str.pyj +5 -3
  31. package/src/compiler.pyj +36 -36
  32. package/src/errors.pyj +30 -30
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/asyncio.pyj +534 -0
  35. package/src/lib/base64.pyj +399 -0
  36. package/src/lib/bisect.pyj +73 -0
  37. package/src/lib/collections.pyj +1 -1
  38. package/src/lib/copy.pyj +120 -120
  39. package/src/lib/csv.pyj +494 -0
  40. package/src/lib/elementmaker.pyj +83 -83
  41. package/src/lib/encodings.pyj +126 -126
  42. package/src/lib/gettext.pyj +569 -569
  43. package/src/lib/heapq.pyj +98 -0
  44. package/src/lib/html.pyj +382 -0
  45. package/src/lib/http/__init__.pyj +98 -0
  46. package/src/lib/http/client.pyj +304 -0
  47. package/src/lib/http/cookies.pyj +236 -0
  48. package/src/lib/itertools.pyj +580 -580
  49. package/src/lib/logging.pyj +672 -0
  50. package/src/lib/math.pyj +193 -193
  51. package/src/lib/operator.pyj +11 -11
  52. package/src/lib/pythonize.pyj +20 -20
  53. package/src/lib/random.pyj +118 -118
  54. package/src/lib/react.pyj +74 -74
  55. package/src/lib/string.pyj +357 -0
  56. package/src/lib/textwrap.pyj +329 -0
  57. package/src/lib/traceback.pyj +63 -63
  58. package/src/lib/urllib/__init__.pyj +14 -0
  59. package/src/lib/urllib/error.pyj +66 -0
  60. package/src/lib/urllib/parse.pyj +475 -0
  61. package/src/lib/urllib/request.pyj +86 -0
  62. package/src/lib/uuid.pyj +77 -77
  63. package/src/monaco-language-service/analyzer.js +5 -2
  64. package/src/monaco-language-service/completions.js +26 -0
  65. package/src/monaco-language-service/diagnostics.js +202 -3
  66. package/src/monaco-language-service/dts.js +550 -550
  67. package/src/monaco-language-service/scope.js +1 -0
  68. package/src/output/comments.pyj +45 -45
  69. package/src/output/exceptions.pyj +201 -201
  70. package/src/output/functions.pyj +152 -6
  71. package/src/output/jsx.pyj +164 -164
  72. package/src/output/loops.pyj +17 -2
  73. package/src/output/modules.pyj +1 -1
  74. package/src/output/operators.pyj +15 -0
  75. package/src/output/stream.pyj +0 -1
  76. package/src/output/treeshake.pyj +182 -182
  77. package/src/output/utils.pyj +72 -72
  78. package/src/parse.pyj +80 -17
  79. package/src/string_interpolation.pyj +72 -72
  80. package/src/tokenizer.pyj +1 -1
  81. package/src/unicode_aliases.pyj +576 -576
  82. package/src/utils.pyj +192 -192
  83. package/test/_import_one.pyj +37 -37
  84. package/test/_import_two/__init__.pyj +11 -11
  85. package/test/_import_two/level2/deep.pyj +4 -4
  86. package/test/_import_two/other.pyj +6 -6
  87. package/test/_import_two/sub.pyj +13 -13
  88. package/test/aes_vectors.pyj +421 -421
  89. package/test/annotations.pyj +80 -80
  90. package/test/async_generators.pyj +144 -0
  91. package/test/asyncio.pyj +307 -0
  92. package/test/base64.pyj +202 -0
  93. package/test/bisect.pyj +178 -0
  94. package/test/csv.pyj +405 -0
  95. package/test/decorators.pyj +77 -77
  96. package/test/docstrings.pyj +39 -39
  97. package/test/elementmaker_test.pyj +45 -45
  98. package/test/float_special.pyj +64 -0
  99. package/test/functions.pyj +151 -151
  100. package/test/generators.pyj +41 -41
  101. package/test/generic.pyj +370 -370
  102. package/test/heapq.pyj +174 -0
  103. package/test/html.pyj +212 -0
  104. package/test/http.pyj +259 -0
  105. package/test/imports.pyj +79 -72
  106. package/test/internationalization.pyj +73 -73
  107. package/test/lint.pyj +164 -164
  108. package/test/logging.pyj +356 -0
  109. package/test/long.pyj +130 -0
  110. package/test/loops.pyj +85 -85
  111. package/test/numpy.pyj +734 -734
  112. package/test/parenthesized_with.pyj +141 -0
  113. package/test/python_compat.pyj +3 -5
  114. package/test/python_modulo.pyj +76 -0
  115. package/test/python_modulo_off.pyj +21 -0
  116. package/test/repl.pyj +121 -121
  117. package/test/scoped_flags.pyj +76 -76
  118. package/test/str.pyj +14 -0
  119. package/test/string.pyj +245 -0
  120. package/test/textwrap.pyj +172 -0
  121. package/test/type_display.pyj +48 -0
  122. package/test/type_enforcement.pyj +164 -0
  123. package/test/unit/index.js +14 -6
  124. package/test/unit/language-service-completions.js +119 -0
  125. package/test/unit/language-service-dts.js +543 -543
  126. package/test/unit/language-service-hover.js +455 -455
  127. package/test/unit/language-service-scope.js +32 -0
  128. package/test/unit/language-service.js +127 -3
  129. package/test/unit/run-language-service.js +17 -3
  130. package/test/unit/web-repl.js +2094 -29
  131. package/test/urllib.pyj +193 -0
  132. package/tools/compile.js +1 -1
  133. package/tools/compiler.d.ts +367 -367
  134. package/tools/completer.js +131 -131
  135. package/tools/embedded_compiler.js +7 -7
  136. package/tools/gettext.js +185 -185
  137. package/tools/ini.js +65 -65
  138. package/tools/msgfmt.js +187 -187
  139. package/tools/repl.js +223 -223
  140. package/tools/test.js +118 -118
  141. package/tools/utils.js +128 -128
  142. package/tools/web_repl.js +95 -95
  143. package/try +41 -41
  144. package/web-repl/env.js +196 -196
  145. package/web-repl/index.html +163 -163
  146. package/web-repl/main.js +1 -1
  147. package/web-repl/prism.css +139 -139
  148. package/web-repl/prism.js +113 -113
  149. package/web-repl/rapydscript.js +224 -224
  150. package/web-repl/sha1.js +25 -25
  151. package/test/omit_function_metadata.pyj +0 -20
package/test/heapq.pyj ADDED
@@ -0,0 +1,174 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # heapq.pyj
5
+ # Tests for the heapq standard library module.
6
+
7
+ from heapq import heappush, heappop, heapify, heapreplace, heappushpop, nlargest, nsmallest
8
+
9
+ ae = assrt.equal
10
+ ade = assrt.deepEqual
11
+ ok = assrt.ok
12
+
13
+ # ── 1. heappush / heappop — basic sort order ──────────────────────────────────
14
+
15
+ h = []
16
+ heappush(h, 3)
17
+ heappush(h, 1)
18
+ heappush(h, 4)
19
+ heappush(h, 1)
20
+ heappush(h, 5)
21
+ ae(heappop(h), 1)
22
+ ae(heappop(h), 1)
23
+ ae(heappop(h), 3)
24
+ ae(heappop(h), 4)
25
+ ae(heappop(h), 5)
26
+ ae(h.length, 0)
27
+
28
+ # ── 2. heapify — min at root ──────────────────────────────────────────────────
29
+
30
+ x = [5, 3, 8, 1, 2, 4]
31
+ heapify(x)
32
+ ae(x[0], 1)
33
+
34
+ # verify heap invariant: every parent <= its children
35
+ _hi = 0
36
+ while _hi < Math.floor(x.length / 2):
37
+ _left = 2 * _hi + 1
38
+ _right = 2 * _hi + 2
39
+ ok(x[_hi] <= x[_left], 'parent <= left child at ' + str(_hi))
40
+ if _right < x.length:
41
+ ok(x[_hi] <= x[_right], 'parent <= right child at ' + str(_hi))
42
+ _hi += 1
43
+
44
+ # ── 3. heapify + heappop produces sorted output ───────────────────────────────
45
+
46
+ _sort_data = [5, 3, 8, 1, 2, 4]
47
+ heapify(_sort_data)
48
+ _sort_result = []
49
+ while _sort_data.length > 0:
50
+ _sort_result.push(heappop(_sort_data))
51
+ ade(_sort_result, [1, 2, 3, 4, 5, 8])
52
+
53
+ # ── 4. heapreplace — returns old min, inserts new item ────────────────────────
54
+
55
+ _r = [1, 3, 5, 7, 9]
56
+ heapify(_r)
57
+ _old = heapreplace(_r, 4)
58
+ ae(_old, 1)
59
+ ae(_r[0], 3)
60
+
61
+ _r_sorted = []
62
+ while _r.length > 0:
63
+ _r_sorted.push(heappop(_r))
64
+ ade(_r_sorted, [3, 4, 5, 7, 9])
65
+
66
+ # ── 5. heappushpop ────────────────────────────────────────────────────────────
67
+
68
+ # item > heap[0]: swap root out, sift new item in
69
+ _pp = [1, 3, 5]
70
+ heapify(_pp)
71
+ _res_pp = heappushpop(_pp, 2)
72
+ ae(_res_pp, 1)
73
+ ae(_pp[0], 2)
74
+
75
+ # item <= heap[0]: return item unchanged, heap unmodified
76
+ _pp2 = [5, 7, 9]
77
+ heapify(_pp2)
78
+ _res_pp2 = heappushpop(_pp2, 4)
79
+ ae(_res_pp2, 4)
80
+ ae(_pp2[0], 5)
81
+
82
+ # ── 6. heappop raises IndexError on empty heap ────────────────────────────────
83
+
84
+ _pop_err = False
85
+ try:
86
+ heappop([])
87
+ except IndexError:
88
+ _pop_err = True
89
+ ok(_pop_err, 'heappop on empty heap raises IndexError')
90
+
91
+ # ── 7. heapreplace raises IndexError on empty heap ───────────────────────────
92
+
93
+ _rep_err = False
94
+ try:
95
+ heapreplace([], 1)
96
+ except IndexError:
97
+ _rep_err = True
98
+ ok(_rep_err, 'heapreplace on empty heap raises IndexError')
99
+
100
+ # ── 8. nsmallest — basic ─────────────────────────────────────────────────────
101
+
102
+ _ns_data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
103
+ ade(nsmallest(3, _ns_data), [1, 1, 2])
104
+ ade(nsmallest(1, _ns_data), [1])
105
+ ade(nsmallest(0, _ns_data), [])
106
+ ade(nsmallest(100, _ns_data), [1, 1, 2, 3, 3, 4, 5, 5, 6, 9])
107
+
108
+ # ── 9. nlargest — basic ──────────────────────────────────────────────────────
109
+
110
+ ade(nlargest(3, _ns_data), [9, 6, 5])
111
+ ade(nlargest(1, _ns_data), [9])
112
+ ade(nlargest(0, _ns_data), [])
113
+ ade(nlargest(100, _ns_data), [9, 6, 5, 5, 4, 3, 3, 2, 1, 1])
114
+
115
+ # ── 10. nsmallest / nlargest with key ────────────────────────────────────────
116
+
117
+ _pairs = [[3, 'c'], [1, 'a'], [4, 'd'], [1, 'b'], [5, 'e']]
118
+ _kfn = def(p): return p[0];
119
+
120
+ _sm2 = nsmallest(2, _pairs, key=_kfn)
121
+ ae(_sm2.length, 2)
122
+ ae(_sm2[0][0], 1)
123
+ ae(_sm2[1][0], 1)
124
+
125
+ _lg2 = nlargest(2, _pairs, key=_kfn)
126
+ ae(_lg2.length, 2)
127
+ ae(_lg2[0][0], 5)
128
+ ae(_lg2[1][0], 4)
129
+
130
+ # ── 11. Single element ────────────────────────────────────────────────────────
131
+
132
+ _single = [42]
133
+ heapify(_single)
134
+ ae(_single[0], 42)
135
+ ae(heappop(_single), 42)
136
+ ae(_single.length, 0)
137
+
138
+ # ── 12. Negative numbers ─────────────────────────────────────────────────────
139
+
140
+ _neg = [-3, -1, -4, -1, -5]
141
+ heapify(_neg)
142
+ _neg_sorted = []
143
+ while _neg.length > 0:
144
+ _neg_sorted.push(heappop(_neg))
145
+ ade(_neg_sorted, [-5, -4, -3, -1, -1])
146
+
147
+ # ── 13. Duplicates ───────────────────────────────────────────────────────────
148
+
149
+ _dups = [2, 2, 2, 1, 1, 1, 3, 3, 3]
150
+ heapify(_dups)
151
+ _dups_sorted = []
152
+ while _dups.length > 0:
153
+ _dups_sorted.push(heappop(_dups))
154
+ ade(_dups_sorted, [1, 1, 1, 2, 2, 2, 3, 3, 3])
155
+
156
+ # ── 14. heappush onto pre-existing heap ──────────────────────────────────────
157
+
158
+ _pre = [2, 5, 8]
159
+ heapify(_pre)
160
+ heappush(_pre, 1)
161
+ ae(_pre[0], 1)
162
+ heappush(_pre, 3)
163
+ _pre_sorted = []
164
+ while _pre.length > 0:
165
+ _pre_sorted.push(heappop(_pre))
166
+ ade(_pre_sorted, [1, 2, 3, 5, 8])
167
+
168
+ # ── 15. nsmallest / nlargest do not mutate original ──────────────────────────
169
+
170
+ _orig = [5, 2, 8, 1, 9]
171
+ _orig_copy = _orig.slice()
172
+ nsmallest(3, _orig)
173
+ nlargest(3, _orig)
174
+ ade(_orig, _orig_copy)
package/test/html.pyj ADDED
@@ -0,0 +1,212 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # html.pyj
5
+ # Tests for the html standard library module.
6
+
7
+ from html import escape, unescape, HTMLParser
8
+
9
+ ae = assrt.equal
10
+ ade = assrt.deepEqual
11
+ ok = assrt.ok
12
+
13
+ # ── 1. escape – individual special characters ─────────────────────────────
14
+ ae(escape('<'), '&lt;')
15
+ ae(escape('>'), '&gt;')
16
+ ae(escape('&'), '&amp;')
17
+ ae(escape('"'), '&quot;')
18
+ ae(escape("'"), '&#x27;')
19
+
20
+ # ── 2. escape – combined string ───────────────────────────────────────────
21
+ ae(escape('<script>alert("xss")</script>'),
22
+ '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;')
23
+
24
+ # ── 3. escape – quote=False leaves quotes unchanged ───────────────────────
25
+ ae(escape('A & B', quote=False), 'A &amp; B')
26
+ ae(escape('"quoted"', quote=False), '"quoted"')
27
+ ae(escape("it's", quote=False), "it's")
28
+
29
+ # ── 4. escape – plain text unchanged ──────────────────────────────────────
30
+ ae(escape('hello world'), 'hello world')
31
+ ae(escape(''), '')
32
+
33
+ # ── 5. unescape – named entities ──────────────────────────────────────────
34
+ ae(unescape('&amp;'), '&')
35
+ ae(unescape('&lt;'), '<')
36
+ ae(unescape('&gt;'), '>')
37
+ ae(unescape('&quot;'), '"')
38
+ ae(unescape('&apos;'), "'")
39
+ ae(unescape('&copy;'), '©')
40
+ ae(unescape('&euro;'), '€')
41
+ ae(unescape('&ndash;'), '–')
42
+ ae(unescape('&mdash;'), '—')
43
+ ae(unescape('&trade;'), '™')
44
+
45
+ # ── 6. unescape – numeric references ──────────────────────────────────────
46
+ ae(unescape('&#65;'), 'A') # decimal
47
+ ae(unescape('&#x41;'), 'A') # hex lowercase
48
+ ae(unescape('&#X41;'), 'A') # hex uppercase
49
+ ae(unescape('&#169;'), '©') # © by decimal
50
+ ae(unescape('&#x20ac;'), '€') # € by hex
51
+
52
+ # ── 7. unescape – high code point (emoji via numeric ref) ─────────────────
53
+ ae(unescape('&#128512;'), chr(128512)) # grinning face emoji
54
+
55
+ # ── 8. unescape – unknown entity left intact ──────────────────────────────
56
+ ae(unescape('&nosuchentity;'), '&nosuchentity;')
57
+
58
+ # ── 9. unescape – mixed string ────────────────────────────────────────────
59
+ ae(unescape('Hello &amp; World &lt;3'), 'Hello & World <3')
60
+
61
+ # ── 10. round-trip ───────────────────────────────────────────────────────
62
+ s = '<a href="http://example.com">Hello & World</a>'
63
+ ae(unescape(escape(s)), s)
64
+
65
+ # ── 11. HTMLParser – start/end/data ──────────────────────────────────────
66
+ class _P1(HTMLParser):
67
+ def __init__(self):
68
+ HTMLParser.__init__(self)
69
+ self.events = []
70
+ def handle_starttag(self, tag, attrs):
71
+ self.events.push(['start', tag, attrs])
72
+ def handle_endtag(self, tag):
73
+ self.events.push(['end', tag])
74
+ def handle_data(self, data):
75
+ self.events.push(['data', data])
76
+
77
+ p1 = _P1()
78
+ p1.feed('<p class="greeting">Hello, World!</p>')
79
+ ae(p1.events[0][0], 'start')
80
+ ae(p1.events[0][1], 'p')
81
+ ae(p1.events[0][2][0][0], 'class')
82
+ ae(p1.events[0][2][0][1], 'greeting')
83
+ ae(p1.events[1][0], 'data')
84
+ ae(p1.events[1][1], 'Hello, World!')
85
+ ae(p1.events[2][0], 'end')
86
+ ae(p1.events[2][1], 'p')
87
+
88
+ # ── 12. HTMLParser – entity auto-conversion in data ───────────────────────
89
+ class _P2(HTMLParser):
90
+ def __init__(self):
91
+ HTMLParser.__init__(self)
92
+ self.chunks = []
93
+ def handle_data(self, data):
94
+ self.chunks.push(data)
95
+
96
+ p2 = _P2()
97
+ p2.feed('<p>Hello &amp; World &lt;3</p>')
98
+ ae(p2.chunks[0], 'Hello & World <3')
99
+
100
+ # ── 13. HTMLParser – comment ──────────────────────────────────────────────
101
+ class _P3(HTMLParser):
102
+ def __init__(self):
103
+ HTMLParser.__init__(self)
104
+ self.comments = []
105
+ def handle_comment(self, data):
106
+ self.comments.push(data)
107
+
108
+ p3 = _P3()
109
+ p3.feed('<!-- this is a comment -->')
110
+ ae(p3.comments.length, 1)
111
+ ae(p3.comments[0], ' this is a comment ')
112
+
113
+ # ── 14. HTMLParser – multiple attributes ─────────────────────────────────
114
+ class _P4(HTMLParser):
115
+ def __init__(self):
116
+ HTMLParser.__init__(self)
117
+ self.attrs = None
118
+ def handle_starttag(self, tag, attrs):
119
+ self.attrs = attrs
120
+
121
+ p4 = _P4()
122
+ p4.feed('<a href="http://example.com" target="_blank" rel="noopener">')
123
+ ae(p4.attrs.length, 3)
124
+ ae(p4.attrs[0][0], 'href')
125
+ ae(p4.attrs[0][1], 'http://example.com')
126
+ ae(p4.attrs[1][0], 'target')
127
+ ae(p4.attrs[1][1], '_blank')
128
+ ae(p4.attrs[2][0], 'rel')
129
+ ae(p4.attrs[2][1], 'noopener')
130
+
131
+ # ── 15. HTMLParser – self-closing tag ─────────────────────────────────────
132
+ class _P5(HTMLParser):
133
+ def __init__(self):
134
+ HTMLParser.__init__(self)
135
+ self.events = []
136
+ def handle_starttag(self, tag, attrs):
137
+ self.events.push('start:' + tag)
138
+ def handle_endtag(self, tag):
139
+ self.events.push('end:' + tag)
140
+
141
+ p5 = _P5()
142
+ p5.feed('<br/>')
143
+ ae(p5.events.length, 2)
144
+ ae(p5.events[0], 'start:br')
145
+ ae(p5.events[1], 'end:br')
146
+
147
+ # ── 16. HTMLParser – get_starttag_text ───────────────────────────────────
148
+ class _P6(HTMLParser):
149
+ def __init__(self):
150
+ HTMLParser.__init__(self)
151
+ self.last_raw = None
152
+ def handle_starttag(self, tag, attrs):
153
+ self.last_raw = self.get_starttag_text()
154
+
155
+ p6 = _P6()
156
+ p6.feed('<img src="pic.png" alt="photo">')
157
+ ae(p6.last_raw, '<img src="pic.png" alt="photo">')
158
+
159
+ # ── 17. HTMLParser – DOCTYPE declaration ─────────────────────────────────
160
+ class _P7(HTMLParser):
161
+ def __init__(self):
162
+ HTMLParser.__init__(self)
163
+ self.decls = []
164
+ def handle_decl(self, decl):
165
+ self.decls.push(decl)
166
+
167
+ p7 = _P7()
168
+ p7.feed('<!DOCTYPE html>')
169
+ ae(p7.decls.length, 1)
170
+ ae(p7.decls[0], 'DOCTYPE html')
171
+
172
+ # ── 18. HTMLParser – tag names lowercased ────────────────────────────────
173
+ class _P8(HTMLParser):
174
+ def __init__(self):
175
+ HTMLParser.__init__(self)
176
+ self.tags = []
177
+ def handle_starttag(self, tag, attrs):
178
+ self.tags.push(tag)
179
+
180
+ p8 = _P8()
181
+ p8.feed('<DIV><SPAN></SPAN></DIV>')
182
+ ae(p8.tags[0], 'div')
183
+ ae(p8.tags[1], 'span')
184
+
185
+ # ── 19. HTMLParser – valueless attribute yields None ──────────────────────
186
+ class _P9(HTMLParser):
187
+ def __init__(self):
188
+ HTMLParser.__init__(self)
189
+ self.attrs = None
190
+ def handle_starttag(self, tag, attrs):
191
+ self.attrs = attrs
192
+
193
+ p9 = _P9()
194
+ p9.feed('<input disabled required>')
195
+ ae(p9.attrs[0][0], 'disabled')
196
+ ae(p9.attrs[0][1], None)
197
+ ae(p9.attrs[1][0], 'required')
198
+ ae(p9.attrs[1][1], None)
199
+
200
+ # ── 20. HTMLParser – incremental feed ────────────────────────────────────
201
+ class _P10(HTMLParser):
202
+ def __init__(self):
203
+ HTMLParser.__init__(self)
204
+ self.data = []
205
+ def handle_data(self, d):
206
+ self.data.push(d)
207
+
208
+ p10 = _P10()
209
+ p10.feed('<p>Hello')
210
+ p10.feed(' World</p>')
211
+ ok(p10.data.length >= 1)
212
+ ae(p10.data.join(''), 'Hello World')
package/test/http.pyj ADDED
@@ -0,0 +1,259 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # http.pyj
5
+ # Tests for the http standard library module.
6
+ #
7
+ # Covers http (HTTPStatus), http.client (pure-JS synchronous parts), and
8
+ # http.cookies. Network-dependent tests (getresponse) are exercised only in
9
+ # the web-repl bundle tests via a mocked fetch environment.
10
+
11
+ from __python__ import overload_getitem
12
+
13
+ from http import HTTPStatus
14
+ from http.client import (HTTPConnection, HTTPSConnection,
15
+ HTTPException, NotConnected, InvalidURL,
16
+ RemoteDisconnected, HTTP_PORT, HTTPS_PORT,
17
+ HTTPResponse)
18
+ from http.cookies import SimpleCookie, Morsel, CookieError
19
+
20
+ ae = assrt.equal
21
+ ade = assrt.deepEqual
22
+ ok = assrt.ok
23
+
24
+ # ── 1. HTTPStatus constants ───────────────────────────────────────────────────
25
+
26
+ ae(HTTPStatus.OK, 200)
27
+ ae(HTTPStatus.CREATED, 201)
28
+ ae(HTTPStatus.NO_CONTENT, 204)
29
+ ae(HTTPStatus.MOVED_PERMANENTLY, 301)
30
+ ae(HTTPStatus.NOT_MODIFIED, 304)
31
+ ae(HTTPStatus.BAD_REQUEST, 400)
32
+ ae(HTTPStatus.UNAUTHORIZED, 401)
33
+ ae(HTTPStatus.FORBIDDEN, 403)
34
+ ae(HTTPStatus.NOT_FOUND, 404)
35
+ ae(HTTPStatus.METHOD_NOT_ALLOWED, 405)
36
+ ae(HTTPStatus.INTERNAL_SERVER_ERROR, 500)
37
+ ae(HTTPStatus.SERVICE_UNAVAILABLE, 503)
38
+ ae(HTTPStatus.IM_A_TEAPOT, 418)
39
+
40
+ # ── 2. Port constants ─────────────────────────────────────────────────────────
41
+
42
+ ae(HTTP_PORT, 80)
43
+ ae(HTTPS_PORT, 443)
44
+
45
+ # ── 3. HTTPException hierarchy ────────────────────────────────────────────────
46
+
47
+ try:
48
+ raise HTTPException('base error')
49
+ ok(False)
50
+ except HTTPException as e:
51
+ ok('base error' in str(e))
52
+
53
+ try:
54
+ raise NotConnected('not connected')
55
+ ok(False)
56
+ except HTTPException as e:
57
+ ok(isinstance(e, NotConnected))
58
+
59
+ try:
60
+ raise InvalidURL('bad url')
61
+ ok(False)
62
+ except HTTPException as e:
63
+ ok(isinstance(e, InvalidURL))
64
+
65
+ try:
66
+ raise RemoteDisconnected('gone')
67
+ ok(False)
68
+ except HTTPException as e:
69
+ ok(isinstance(e, RemoteDisconnected))
70
+
71
+ # ── 4. HTTPConnection — _build_url ────────────────────────────────────────────
72
+
73
+ conn = HTTPConnection('example.com')
74
+ ae(conn._build_url('/path'), 'http://example.com/path')
75
+ ae(conn._build_url('/'), 'http://example.com/')
76
+
77
+ conn2 = HTTPConnection('example.com', 8080)
78
+ ae(conn2._build_url('/api'), 'http://example.com:8080/api')
79
+
80
+ # Default port 80 should be omitted
81
+ conn3 = HTTPConnection('example.com', 80)
82
+ ae(conn3._build_url('/'), 'http://example.com/')
83
+
84
+ # Full URL passes through unchanged
85
+ ae(conn._build_url('https://other.com/x'), 'https://other.com/x')
86
+ ae(conn._build_url('http://other.com/x'), 'http://other.com/x')
87
+
88
+ # ── 5. HTTPSConnection — _build_url ──────────────────────────────────────────
89
+
90
+ sconn = HTTPSConnection('secure.example.com')
91
+ ae(sconn._build_url('/data'), 'https://secure.example.com/data')
92
+
93
+ sconn2 = HTTPSConnection('secure.example.com', 8443)
94
+ ae(sconn2._build_url('/data'), 'https://secure.example.com:8443/data')
95
+
96
+ # Default port 443 should be omitted
97
+ sconn3 = HTTPSConnection('secure.example.com', 443)
98
+ ae(sconn3._build_url('/data'), 'https://secure.example.com/data')
99
+
100
+ # ── 6. HTTPConnection.request stores state ───────────────────────────────────
101
+
102
+ conn4 = HTTPConnection('api.example.com')
103
+ conn4.request('POST', '/items', body='a=1', headers={'Content-Type': 'text/plain'})
104
+ ae(conn4._method, 'POST')
105
+ ae(conn4._path, '/items')
106
+ ae(conn4._body, 'a=1')
107
+ ae(conn4._headers['content-type'], 'text/plain')
108
+
109
+ # ── 7. putrequest / putheader / endheaders ───────────────────────────────────
110
+
111
+ conn5 = HTTPConnection('example.com')
112
+ conn5.putrequest('GET', '/search')
113
+ conn5.putheader('Accept', 'application/json')
114
+ conn5.putheader('X-Custom', 'hello')
115
+ conn5.endheaders()
116
+ ae(conn5._method, 'GET')
117
+ ae(conn5._path, '/search')
118
+ ae(conn5._headers['accept'], 'application/json')
119
+ ae(conn5._headers['x-custom'], 'hello')
120
+ ae(conn5._body, None)
121
+
122
+ conn5b = HTTPConnection('example.com')
123
+ conn5b.putrequest('POST', '/upload')
124
+ conn5b.endheaders('payload')
125
+ ae(conn5b._body, 'payload')
126
+
127
+ # ── 8. HTTPConnection.close resets state ─────────────────────────────────────
128
+
129
+ conn6 = HTTPConnection('example.com')
130
+ conn6.request('DELETE', '/items/1')
131
+ conn6.close()
132
+ ae(conn6._method, None)
133
+ ae(conn6._path, None)
134
+ ae(conn6._body, None)
135
+
136
+ # ── 9. HTTPResponse — sync accessors ─────────────────────────────────────────
137
+
138
+ hdrs = {'content-type': 'application/json', 'x-rate-limit': '100'}
139
+ resp = HTTPResponse(200, 'OK', hdrs, '{"ok":true}', 'https://example.com/api')
140
+ ae(resp.status, 200)
141
+ ae(resp.reason, 'OK')
142
+ ae(resp.url, 'https://example.com/api')
143
+ ae(resp.getheader('content-type'), 'application/json')
144
+ ae(resp.getheader('Content-Type'), 'application/json')
145
+ ae(resp.getheader('missing'), None)
146
+ ae(resp.getheader('missing', 'default'), 'default')
147
+ ok(not resp.closed)
148
+ resp.close()
149
+ ok(resp.closed)
150
+
151
+ # ── 10. HTTPResponse.getheaders ───────────────────────────────────────────────
152
+
153
+ pairs = resp.getheaders()
154
+ ae(len(pairs), 2)
155
+ found = False
156
+ for pair in pairs:
157
+ if pair[0] is 'content-type' and pair[1] is 'application/json':
158
+ found = True
159
+ ok(found)
160
+
161
+ # ── 11. HTTPResponse.read / .json return Promises ────────────────────────────
162
+
163
+ resp2 = HTTPResponse(200, 'OK', {}, '{"n":42}', 'https://x.com/')
164
+ read_p = resp2.read()
165
+ json_p = resp2.json()
166
+ ok(read_p is not None)
167
+ ok(json_p is not None)
168
+ ok(jstype(read_p.then) is 'function')
169
+ ok(jstype(json_p.then) is 'function')
170
+
171
+ # ── 12. SimpleCookie — basic parsing ─────────────────────────────────────────
172
+
173
+ c = SimpleCookie()
174
+ c.load('session=abc123; user=alice')
175
+ ok('session' in c.keys())
176
+ ok('user' in c.keys())
177
+ ae(c['session'].value, 'abc123')
178
+ ae(c['user'].value, 'alice')
179
+
180
+ # ── 13. SimpleCookie — set a cookie ──────────────────────────────────────────
181
+
182
+ c2 = SimpleCookie()
183
+ c2['token'] = 'xyz789'
184
+ ok('token' in c2.keys())
185
+ ae(c2['token'].value, 'xyz789')
186
+ ae(c2['token'].coded_value, 'xyz789')
187
+
188
+ # ── 14. SimpleCookie — cookie attributes ─────────────────────────────────────
189
+
190
+ c3 = SimpleCookie()
191
+ c3['id'] = '1'
192
+ c3['id']['path'] = '/'
193
+ c3['id']['max-age'] = 3600
194
+ c3['id']['secure'] = True
195
+ ae(c3['id']['path'], '/')
196
+ ae(c3['id']['max-age'], 3600)
197
+ ok(c3['id']['secure'])
198
+
199
+ # ── 15. Morsel.OutputString ───────────────────────────────────────────────────
200
+
201
+ m = Morsel()
202
+ m.set('token', 'abc', 'abc')
203
+ m._attrs['path'] = '/'
204
+ m._attrs['max-age'] = '3600'
205
+ s = m.OutputString()
206
+ ok('token=abc' in s)
207
+ ok('Path=/' in s)
208
+ ok('Max-Age=3600' in s)
209
+
210
+ # ── 16. Morsel.output includes header ────────────────────────────────────────
211
+
212
+ out = m.output()
213
+ ok('Set-Cookie: token=abc' in out)
214
+
215
+ out2 = m.output('Set-Cookie')
216
+ ok('Set-Cookie: token=abc' in out2)
217
+
218
+ # ── 17. SimpleCookie.output ───────────────────────────────────────────────────
219
+
220
+ c4 = SimpleCookie()
221
+ c4['a'] = '1'
222
+ c4['b'] = '2'
223
+ full = c4.output()
224
+ ok('Set-Cookie: a=1' in full)
225
+ ok('Set-Cookie: b=2' in full)
226
+
227
+ # ── 18. SimpleCookie constructor with initial data ────────────────────────────
228
+
229
+ c5 = SimpleCookie('x=10; y=20')
230
+ ae(c5['x'].value, '10')
231
+ ae(c5['y'].value, '20')
232
+
233
+ # ── 19. SimpleCookie.keys / values / items ───────────────────────────────────
234
+
235
+ c6 = SimpleCookie()
236
+ c6['p'] = 'hello'
237
+ c6['q'] = 'world'
238
+ ks = c6.keys()
239
+ ok('p' in ks) # ks is a list — 'in' checks array membership
240
+ ok('q' in ks)
241
+ vs = c6.values()
242
+ ae(len(vs), 2)
243
+ itms = c6.items()
244
+ ae(len(itms), 2)
245
+
246
+ # ── 20. CookieError is an Exception ──────────────────────────────────────────
247
+
248
+ try:
249
+ raise CookieError('bad cookie')
250
+ ok(False)
251
+ except CookieError as e:
252
+ ok('bad cookie' in str(e))
253
+
254
+ # ── 21. set_debuglevel / connect no-op ───────────────────────────────────────
255
+
256
+ conn7 = HTTPConnection('example.com')
257
+ conn7.set_debuglevel(1)
258
+ conn7.connect()
259
+ ok(True)