@voxgig/sdkgen 0.44.0 → 1.0.1
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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/ReadmeEntity.js +9 -153
- package/dist/cmp/ReadmeEntity.js.map +1 -1
- package/dist/cmp/ReadmeIntro.js +9 -14
- package/dist/cmp/ReadmeIntro.js.map +1 -1
- package/dist/cmp/ReadmeModel.js +6 -4
- package/dist/cmp/ReadmeModel.js.map +1 -1
- package/dist/cmp/ReadmeOptions.js +9 -61
- package/dist/cmp/ReadmeOptions.js.map +1 -1
- package/dist/cmp/ReadmeRef.js +10 -1328
- package/dist/cmp/ReadmeRef.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +2 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +9 -0
- package/dist/utility.js.map +1 -1
- package/package.json +3 -3
- package/project/.sdk/src/cmp/go/Config_go.ts +9 -4
- package/project/.sdk/src/cmp/go/Entity_go.ts +2 -2
- package/project/.sdk/src/cmp/go/Main_go.ts +8 -4
- package/project/.sdk/src/cmp/go/Package_go.ts +2 -2
- package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +138 -0
- package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +2 -2
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +8 -5
- package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +2 -2
- package/project/.sdk/src/cmp/go/ReadmeIntro_go.ts +18 -0
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +8 -5
- package/project/.sdk/src/cmp/go/ReadmeOptions_go.ts +58 -0
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +13 -9
- package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +354 -0
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +8 -6
- package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +2 -2
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +222 -41
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +142 -60
- package/project/.sdk/src/cmp/go/Test_go.ts +2 -2
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +21 -4
- package/project/.sdk/src/cmp/js/Config_js.ts +18 -0
- package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +138 -0
- package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +11 -6
- package/project/.sdk/src/cmp/js/ReadmeIntro_js.ts +18 -0
- package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +6 -3
- package/project/.sdk/src/cmp/js/ReadmeOptions_js.ts +58 -0
- package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +6 -4
- package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +384 -0
- package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +6 -4
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +23 -12
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +107 -74
- package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +1 -5
- package/project/.sdk/src/cmp/lua/Config_lua.ts +9 -4
- package/project/.sdk/src/cmp/lua/Package_lua.ts +9 -2
- package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +138 -0
- package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +6 -3
- package/project/.sdk/src/cmp/lua/ReadmeIntro_lua.ts +18 -0
- package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +6 -3
- package/project/.sdk/src/cmp/lua/ReadmeOptions_lua.ts +58 -0
- package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +6 -4
- package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +360 -0
- package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +6 -4
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +172 -29
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +120 -52
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +20 -4
- package/project/.sdk/src/cmp/php/Config_php.ts +10 -8
- package/project/.sdk/src/cmp/php/Package_php.ts +7 -1
- package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +138 -0
- package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +6 -3
- package/project/.sdk/src/cmp/php/ReadmeIntro_php.ts +18 -0
- package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +6 -3
- package/project/.sdk/src/cmp/php/ReadmeOptions_php.ts +58 -0
- package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +6 -4
- package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +358 -0
- package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +6 -4
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +171 -28
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +126 -55
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +17 -3
- package/project/.sdk/src/cmp/py/Config_py.ts +9 -4
- package/project/.sdk/src/cmp/py/Package_py.ts +8 -1
- package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +138 -0
- package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +6 -3
- package/project/.sdk/src/cmp/py/ReadmeIntro_py.ts +18 -0
- package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +6 -3
- package/project/.sdk/src/cmp/py/ReadmeOptions_py.ts +58 -0
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +9 -6
- package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +356 -0
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +9 -6
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +164 -27
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +125 -51
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +19 -4
- package/project/.sdk/src/cmp/rb/Config_rb.ts +9 -4
- package/project/.sdk/src/cmp/rb/Package_rb.ts +9 -2
- package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +138 -0
- package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +6 -3
- package/project/.sdk/src/cmp/rb/ReadmeIntro_rb.ts +18 -0
- package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +6 -3
- package/project/.sdk/src/cmp/rb/ReadmeOptions_rb.ts +58 -0
- package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +6 -4
- package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +361 -0
- package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +6 -4
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +172 -29
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +120 -52
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +19 -3
- package/project/.sdk/src/cmp/ts/Config_ts.ts +18 -0
- package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
- package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +138 -0
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +11 -6
- package/project/.sdk/src/cmp/ts/ReadmeIntro_ts.ts +18 -0
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +9 -5
- package/project/.sdk/src/cmp/ts/ReadmeOptions_ts.ts +58 -0
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +6 -4
- package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +384 -0
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +6 -4
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +213 -42
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +168 -75
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +1 -5
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +8 -1
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +8 -2
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +21 -1
- package/project/.sdk/tm/go/feature/test_feature.go +51 -3
- package/project/.sdk/tm/go/test/runner_test.go +106 -6
- package/project/.sdk/tm/go/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/go/utility/fetcher.go +10 -0
- package/project/.sdk/tm/go/utility/make_url.go +12 -0
- package/project/.sdk/tm/go/utility/prepare_auth.go +15 -1
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +7 -1
- package/project/.sdk/tm/lua/feature/test_feature.lua +41 -3
- package/project/.sdk/tm/lua/test/runner.lua +74 -0
- package/project/.sdk/tm/lua/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/lua/utility/fetcher.lua +13 -0
- package/project/.sdk/tm/lua/utility/make_url.lua +16 -0
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +9 -1
- package/project/.sdk/tm/php/feature/TestFeature.php +185 -43
- package/project/.sdk/tm/php/test/Runner.php +62 -0
- package/project/.sdk/tm/php/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +132 -9
- package/project/.sdk/tm/php/utility/MakeUrl.php +16 -0
- package/project/.sdk/tm/php/utility/PrepareAuth.php +11 -1
- package/project/.sdk/tm/py/feature/test_feature.py +35 -3
- package/project/.sdk/tm/py/test/runner.py +60 -0
- package/project/.sdk/tm/py/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/py/utility/fetcher.py +13 -0
- package/project/.sdk/tm/py/utility/make_url.py +13 -0
- package/project/.sdk/tm/py/utility/prepare_auth.py +10 -1
- package/project/.sdk/tm/rb/feature/test_feature.rb +36 -3
- package/project/.sdk/tm/rb/test/runner.rb +46 -0
- package/project/.sdk/tm/rb/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/rb/utility/fetcher.rb +49 -28
- package/project/.sdk/tm/rb/utility/make_url.rb +16 -0
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +8 -1
- package/project/.sdk/tm/ts/src/utility/MakeUrlUtility.ts +7 -8
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +7 -1
- package/project/.sdk/tm/ts/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/ts/test/utility.ts +120 -2
- package/src/cmp/ReadmeEntity.ts +11 -178
- package/src/cmp/ReadmeIntro.ts +11 -25
- package/src/cmp/ReadmeModel.ts +7 -5
- package/src/cmp/ReadmeOptions.ts +12 -74
- package/src/cmp/ReadmeRef.ts +11 -1372
- package/src/sdkgen.ts +2 -1
- package/src/utility.ts +12 -0
- /package/project/.sdk/tm/go/utility/{make_target.go → make_point.go} +0 -0
|
@@ -62,6 +62,54 @@ class ProjectNameTestRunner:
|
|
|
62
62
|
|
|
63
63
|
return m
|
|
64
64
|
|
|
65
|
+
_test_control = None
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def load_test_control():
|
|
69
|
+
"""Load sdk-test-control.json from this test dir; cache after first read.
|
|
70
|
+
Returns a dict with the empty-skip default if the file is missing or invalid
|
|
71
|
+
so tests never crash on a bad config.
|
|
72
|
+
"""
|
|
73
|
+
if ProjectNameTestRunner._test_control is not None:
|
|
74
|
+
return ProjectNameTestRunner._test_control
|
|
75
|
+
ctrl_path = os.path.join(os.path.dirname(__file__), "sdk-test-control.json")
|
|
76
|
+
try:
|
|
77
|
+
with open(ctrl_path, "r") as f:
|
|
78
|
+
ProjectNameTestRunner._test_control = json.load(f)
|
|
79
|
+
except (FileNotFoundError, IOError, ValueError):
|
|
80
|
+
ProjectNameTestRunner._test_control = {
|
|
81
|
+
"version": 1,
|
|
82
|
+
"test": {"skip": {
|
|
83
|
+
"live": {"direct": [], "entityOp": []},
|
|
84
|
+
"unit": {"direct": [], "entityOp": []},
|
|
85
|
+
}},
|
|
86
|
+
}
|
|
87
|
+
return ProjectNameTestRunner._test_control
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def is_control_skipped(kind, name, mode):
|
|
91
|
+
"""Check sdk-test-control.json for a skip entry. Returns (skip, reason)."""
|
|
92
|
+
ctrl = ProjectNameTestRunner.load_test_control()
|
|
93
|
+
skip = ctrl.get("test", {}).get("skip", {}).get(mode, {}) or {}
|
|
94
|
+
items = skip.get(kind, []) or []
|
|
95
|
+
for item in items:
|
|
96
|
+
if kind == "direct" and item.get("test") == name:
|
|
97
|
+
return True, item.get("reason")
|
|
98
|
+
if kind == "entityOp":
|
|
99
|
+
key = (item.get("entity") or "") + "." + (item.get("op") or "")
|
|
100
|
+
if key == name:
|
|
101
|
+
return True, item.get("reason")
|
|
102
|
+
return False, None
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def live_delay_ms():
|
|
106
|
+
"""Per-test live pacing delay (ms); default 500."""
|
|
107
|
+
ctrl = ProjectNameTestRunner.load_test_control()
|
|
108
|
+
v = ctrl.get("test", {}).get("live", {}).get("delayMs")
|
|
109
|
+
if isinstance(v, int) and v >= 0:
|
|
110
|
+
return v
|
|
111
|
+
return 500
|
|
112
|
+
|
|
65
113
|
@staticmethod
|
|
66
114
|
def entity_list_to_data(lst):
|
|
67
115
|
out = []
|
|
@@ -88,3 +136,15 @@ def env_override(m):
|
|
|
88
136
|
|
|
89
137
|
def entity_list_to_data(lst):
|
|
90
138
|
return ProjectNameTestRunner.entity_list_to_data(lst)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def is_control_skipped(kind, name, mode):
|
|
142
|
+
return ProjectNameTestRunner.is_control_skipped(kind, name, mode)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def load_test_control():
|
|
146
|
+
return ProjectNameTestRunner.load_test_control()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def live_delay_ms():
|
|
150
|
+
return ProjectNameTestRunner.live_delay_ms()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"_doc": "Per-SDK test control. Lists tests/operations to skip in unit and live modes; tunes per-test pacing. Edit by hand. Loaded by py test runner.",
|
|
4
|
+
"test": {
|
|
5
|
+
"skip": {
|
|
6
|
+
"live": {
|
|
7
|
+
"direct": [],
|
|
8
|
+
"entityOp": []
|
|
9
|
+
},
|
|
10
|
+
"unit": {
|
|
11
|
+
"direct": [],
|
|
12
|
+
"entityOp": []
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"live": {
|
|
16
|
+
"delayMs": 500
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -5,6 +5,14 @@ import json
|
|
|
5
5
|
from utility.voxgig_struct import voxgig_struct as vs
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
# Default User-Agent — many CDNs (notably Cloudflare) reject requests with
|
|
9
|
+
# Python's default urllib UA ("Python-urllib/3.x"), returning 403 before
|
|
10
|
+
# the request even reaches the origin. Set a Mozilla-shaped UA so the SDK
|
|
11
|
+
# behaves like every other HTTP client by default. Users can still override
|
|
12
|
+
# by passing a User-Agent header in fetchdef.
|
|
13
|
+
_DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)"
|
|
14
|
+
|
|
15
|
+
|
|
8
16
|
def _default_http_fetch(fullurl, fetchdef):
|
|
9
17
|
import urllib.request
|
|
10
18
|
import urllib.error
|
|
@@ -19,8 +27,13 @@ def _default_http_fetch(fullurl, fetchdef):
|
|
|
19
27
|
data = body_str.encode("utf-8") if body_str is not None else None
|
|
20
28
|
|
|
21
29
|
req = urllib.request.Request(fullurl, data=data, method=method)
|
|
30
|
+
has_ua = False
|
|
22
31
|
for k, v in headers.items():
|
|
32
|
+
if k.lower() == "user-agent":
|
|
33
|
+
has_ua = True
|
|
23
34
|
req.add_header(k, v)
|
|
35
|
+
if not has_ua:
|
|
36
|
+
req.add_header("User-Agent", _DEFAULT_USER_AGENT)
|
|
24
37
|
|
|
25
38
|
try:
|
|
26
39
|
resp = urllib.request.urlopen(req)
|
|
@@ -29,6 +29,19 @@ def make_url_util(ctx):
|
|
|
29
29
|
url = url.replace("{" + key + "}", encoded)
|
|
30
30
|
resmatch[key] = val
|
|
31
31
|
|
|
32
|
+
# Append query string from spec.query.
|
|
33
|
+
qsep = "?"
|
|
34
|
+
query_items = vs.items(getattr(spec, "query", None))
|
|
35
|
+
if query_items is not None:
|
|
36
|
+
for item in query_items:
|
|
37
|
+
key = item[0]
|
|
38
|
+
val = item[1]
|
|
39
|
+
if val is not None and isinstance(key, str):
|
|
40
|
+
val_str = val if isinstance(val, str) else str(val)
|
|
41
|
+
url += qsep + vs.escurl(key) + "=" + vs.escurl(val_str)
|
|
42
|
+
qsep = "&"
|
|
43
|
+
resmatch[key] = val
|
|
44
|
+
|
|
32
45
|
result.resmatch = resmatch
|
|
33
46
|
|
|
34
47
|
return url, None
|
|
@@ -17,9 +17,18 @@ def prepare_auth_util(ctx):
|
|
|
17
17
|
headers = spec.headers
|
|
18
18
|
options = ctx.client.options_map()
|
|
19
19
|
|
|
20
|
+
# Public APIs that need no auth omit the options.auth block entirely.
|
|
21
|
+
if options.get("auth") is None:
|
|
22
|
+
headers.pop(HEADER_AUTH, None)
|
|
23
|
+
return spec, None
|
|
24
|
+
|
|
20
25
|
apikey = vs.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
21
26
|
|
|
22
|
-
if
|
|
27
|
+
if (
|
|
28
|
+
(isinstance(apikey, str) and apikey == NOT_FOUND)
|
|
29
|
+
or apikey is None
|
|
30
|
+
or apikey == ""
|
|
31
|
+
):
|
|
23
32
|
headers.pop(HEADER_AUTH, None)
|
|
24
33
|
else:
|
|
25
34
|
auth_prefix = ""
|
|
@@ -48,8 +48,22 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
|
|
|
48
48
|
entmap = VoxgigStruct.getprop(entity, op.entity)
|
|
49
49
|
entmap = {} unless entmap.is_a?(Hash)
|
|
50
50
|
|
|
51
|
+
# For single-entity ops (load, remove) with an empty explicit match, fall
|
|
52
|
+
# back to the id the entity client already knows from a prior create/load
|
|
53
|
+
# (in fctx.match / fctx.data). Mirrors the TS mock where param() resolves
|
|
54
|
+
# the id from that accumulated state.
|
|
55
|
+
resolve_match = lambda do |explicit|
|
|
56
|
+
return explicit if explicit.is_a?(Hash) && !explicit.empty?
|
|
57
|
+
[fctx.match, fctx.data].each do |src|
|
|
58
|
+
next if src.nil?
|
|
59
|
+
v = VoxgigStruct.getprop(src, "id")
|
|
60
|
+
return { "id" => v } if !v.nil? && v != "__UNDEFINED__"
|
|
61
|
+
end
|
|
62
|
+
{}
|
|
63
|
+
end
|
|
64
|
+
|
|
51
65
|
if op.name == "load"
|
|
52
|
-
args = test_self.build_args(fctx, op, fctx.reqmatch)
|
|
66
|
+
args = test_self.build_args(fctx, op, resolve_match.call(fctx.reqmatch))
|
|
53
67
|
found = VoxgigStruct.select(entmap, args)
|
|
54
68
|
ent = VoxgigStruct.getelem(found, 0)
|
|
55
69
|
return respond.call(404, nil, { "statusText" => "Not found" }) unless ent
|
|
@@ -68,9 +82,28 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
|
|
|
68
82
|
respond.call(200, out, nil)
|
|
69
83
|
|
|
70
84
|
elsif op.name == "update"
|
|
71
|
-
|
|
85
|
+
# Match the existing entity by id only (or its alias). reqdata also
|
|
86
|
+
# contains the new field values, which would otherwise cause select
|
|
87
|
+
# to filter out the entity we want to update. Falls back to first
|
|
88
|
+
# entity when no id present and no match found, mirroring the TS
|
|
89
|
+
# mock's empirical behavior where param(undef) collapses to "no
|
|
90
|
+
# constraint" and select returns all.
|
|
91
|
+
update_match = {}
|
|
92
|
+
if fctx.reqdata.is_a?(Hash)
|
|
93
|
+
update_match["id"] = fctx.reqdata["id"] if fctx.reqdata.key?("id")
|
|
94
|
+
if op.alias_map
|
|
95
|
+
alias_id = VoxgigStruct.getprop(op.alias_map, "id")
|
|
96
|
+
if alias_id && fctx.reqdata.key?(alias_id)
|
|
97
|
+
update_match[alias_id] = fctx.reqdata[alias_id]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
args = test_self.build_args(fctx, op, update_match)
|
|
72
102
|
found = VoxgigStruct.select(entmap, args)
|
|
73
103
|
ent = VoxgigStruct.getelem(found, 0)
|
|
104
|
+
if ent.nil? && entmap.is_a?(Hash) && !entmap.empty?
|
|
105
|
+
ent = entmap.values.find { |e| e.is_a?(Hash) }
|
|
106
|
+
end
|
|
74
107
|
return respond.call(404, nil, { "statusText" => "Not found" }) unless ent
|
|
75
108
|
if ent.is_a?(Hash) && fctx.reqdata
|
|
76
109
|
fctx.reqdata.each { |k, v| ent[k] = v }
|
|
@@ -80,7 +113,7 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
|
|
|
80
113
|
respond.call(200, out, nil)
|
|
81
114
|
|
|
82
115
|
elsif op.name == "remove"
|
|
83
|
-
args = test_self.build_args(fctx, op, fctx.reqmatch)
|
|
116
|
+
args = test_self.build_args(fctx, op, resolve_match.call(fctx.reqmatch))
|
|
84
117
|
found = VoxgigStruct.select(entmap, args)
|
|
85
118
|
ent = VoxgigStruct.getelem(found, 0)
|
|
86
119
|
return respond.call(404, nil, { "statusText" => "Not found" }) unless ent
|
|
@@ -62,6 +62,52 @@ module ProjectNameTestRunner
|
|
|
62
62
|
end
|
|
63
63
|
out
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
@test_control = nil
|
|
67
|
+
|
|
68
|
+
# Load sdk-test-control.json from this test dir; cache. Returns the
|
|
69
|
+
# empty-skip default if the file is missing or invalid.
|
|
70
|
+
def self.load_test_control
|
|
71
|
+
return @test_control unless @test_control.nil?
|
|
72
|
+
ctrl_path = File.join(File.dirname(__FILE__), 'sdk-test-control.json')
|
|
73
|
+
@test_control = begin
|
|
74
|
+
JSON.parse(File.read(ctrl_path))
|
|
75
|
+
rescue StandardError
|
|
76
|
+
{
|
|
77
|
+
'version' => 1,
|
|
78
|
+
'test' => { 'skip' => {
|
|
79
|
+
'live' => { 'direct' => [], 'entityOp' => [] },
|
|
80
|
+
'unit' => { 'direct' => [], 'entityOp' => [] },
|
|
81
|
+
}},
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
@test_control
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Check sdk-test-control.json for a skip entry. Returns [skip, reason].
|
|
88
|
+
def self.is_control_skipped(kind, name, mode)
|
|
89
|
+
ctrl = load_test_control
|
|
90
|
+
skip = (ctrl.dig('test', 'skip', mode) || {})
|
|
91
|
+
items = skip[kind] || []
|
|
92
|
+
items.each do |item|
|
|
93
|
+
if kind == 'direct' && item['test'] == name
|
|
94
|
+
return [true, item['reason']]
|
|
95
|
+
end
|
|
96
|
+
if kind == 'entityOp'
|
|
97
|
+
key = "#{item['entity']}.#{item['op']}"
|
|
98
|
+
return [true, item['reason']] if key == name
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
[false, nil]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Per-test live pacing delay (ms); default 500.
|
|
105
|
+
def self.live_delay_ms
|
|
106
|
+
ctrl = load_test_control
|
|
107
|
+
v = ctrl.dig('test', 'live', 'delayMs')
|
|
108
|
+
return v if v.is_a?(Integer) && v >= 0
|
|
109
|
+
500
|
|
110
|
+
end
|
|
65
111
|
end
|
|
66
112
|
|
|
67
113
|
# Module-level aliases for test convenience.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"_doc": "Per-SDK test control. Lists tests/operations to skip in unit and live modes; tunes per-test pacing. Edit by hand. Loaded by rb test runner.",
|
|
4
|
+
"test": {
|
|
5
|
+
"skip": {
|
|
6
|
+
"live": {
|
|
7
|
+
"direct": [],
|
|
8
|
+
"entityOp": []
|
|
9
|
+
},
|
|
10
|
+
"unit": {
|
|
11
|
+
"direct": [],
|
|
12
|
+
"entityOp": []
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"live": {
|
|
16
|
+
"delayMs": 500
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -10,39 +10,60 @@ module ProjectNameUtilities
|
|
|
10
10
|
body_str = fetchdef["body"]
|
|
11
11
|
headers = fetchdef["headers"] || {}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
begin
|
|
14
|
+
uri = URI.parse(fullurl)
|
|
15
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
16
|
+
http.use_ssl = (uri.scheme == "https")
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
klass = case method_str.upcase
|
|
19
|
+
when "POST" then Net::HTTP::Post
|
|
20
|
+
when "PUT" then Net::HTTP::Put
|
|
21
|
+
when "DELETE" then Net::HTTP::Delete
|
|
22
|
+
when "PATCH" then Net::HTTP::Patch
|
|
23
|
+
else Net::HTTP::Get
|
|
24
|
+
end
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
request = klass.new(uri)
|
|
27
|
+
has_ua = false
|
|
28
|
+
headers.each do |k, v|
|
|
29
|
+
next unless v.is_a?(String)
|
|
30
|
+
has_ua = true if k.to_s.downcase == 'user-agent'
|
|
31
|
+
request[k] = v.to_s
|
|
32
|
+
end
|
|
33
|
+
# Default User-Agent — Net::HTTP sets "Ruby" which some CDNs block.
|
|
34
|
+
# Use a Mozilla-shaped UA unless the caller already set one.
|
|
35
|
+
request['User-Agent'] = 'Mozilla/5.0 (compatible; ProjectNameSDK/1.0)' unless has_ua
|
|
36
|
+
request.body = body_str if body_str.is_a?(String)
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
resp = http.request(request)
|
|
39
|
+
resp_headers = {}
|
|
40
|
+
resp.each_header { |k, v| resp_headers[k.downcase] = v }
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
json_body = nil
|
|
43
|
+
begin
|
|
44
|
+
json_body = JSON.parse(resp.body) if resp.body && !resp.body.empty?
|
|
45
|
+
rescue JSON::ParserError
|
|
46
|
+
end
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
return {
|
|
49
|
+
"status" => resp.code.to_i,
|
|
50
|
+
"statusText" => resp.message,
|
|
51
|
+
"headers" => resp_headers,
|
|
52
|
+
"json" => -> { json_body },
|
|
53
|
+
"body" => resp.body,
|
|
54
|
+
}, nil
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
# Network-level failures (DNS, TCP, TLS, timeouts) — return a synthesized
|
|
57
|
+
# response with status 0 so callers can branch on result.ok like any
|
|
58
|
+
# other failed request, instead of seeing an unhandled exception.
|
|
59
|
+
return {
|
|
60
|
+
"status" => 0,
|
|
61
|
+
"statusText" => "#{e.class}: #{e.message}",
|
|
62
|
+
"headers" => {},
|
|
63
|
+
"json" => -> { nil },
|
|
64
|
+
"body" => nil,
|
|
65
|
+
}, nil
|
|
66
|
+
end
|
|
46
67
|
}
|
|
47
68
|
|
|
48
69
|
Fetcher = ->(ctx, fullurl, fetchdef) {
|
|
@@ -26,6 +26,22 @@ module ProjectNameUtilities
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Append query string from spec.query.
|
|
30
|
+
qsep = "?"
|
|
31
|
+
query_items = VoxgigStruct.items(spec.respond_to?(:query) ? spec.query : nil)
|
|
32
|
+
if query_items
|
|
33
|
+
query_items.each do |item|
|
|
34
|
+
key = item[0]
|
|
35
|
+
val = item[1]
|
|
36
|
+
if val && key.is_a?(String)
|
|
37
|
+
val_str = val.is_a?(String) ? val : val.to_s
|
|
38
|
+
url += qsep + VoxgigStruct.escurl(key) + "=" + VoxgigStruct.escurl(val_str)
|
|
39
|
+
qsep = "&"
|
|
40
|
+
resmatch[key] = val
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
29
45
|
result.resmatch = resmatch
|
|
30
46
|
return url, nil
|
|
31
47
|
}
|
|
@@ -11,9 +11,16 @@ module ProjectNameUtilities
|
|
|
11
11
|
|
|
12
12
|
headers = spec.headers
|
|
13
13
|
options = ctx.client.options_map
|
|
14
|
+
|
|
15
|
+
# Public APIs that need no auth omit the options.auth block entirely.
|
|
16
|
+
if options["auth"].nil?
|
|
17
|
+
headers.delete(HEADER_AUTH)
|
|
18
|
+
return spec, nil
|
|
19
|
+
end
|
|
20
|
+
|
|
14
21
|
apikey = VoxgigStruct.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
15
22
|
|
|
16
|
-
if apikey.is_a?(String) && apikey == NOT_FOUND
|
|
23
|
+
if apikey.nil? || (apikey.is_a?(String) && (apikey == NOT_FOUND || apikey == ""))
|
|
17
24
|
headers.delete(HEADER_AUTH)
|
|
18
25
|
else
|
|
19
26
|
auth_prefix = VoxgigStruct.getpath(options, "auth.prefix") || ""
|
|
@@ -37,18 +37,17 @@ function makeUrl(ctx: Context): Error | string {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// Append query string from spec.query. Entity ops populate this via
|
|
41
|
+
// PrepareQueryUtility from the operation's reqmatch; direct() callers
|
|
42
|
+
// pass it as fetchargs.query.
|
|
41
43
|
let qsep = '?'
|
|
42
44
|
for (let [key, val] of items(spec.query)) {
|
|
43
|
-
if (null
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
resmatch[key] = val
|
|
48
|
-
}
|
|
45
|
+
if (null != val) {
|
|
46
|
+
url += qsep + escurl(key) + '=' + escurl(val)
|
|
47
|
+
qsep = '&'
|
|
48
|
+
resmatch[key] = val
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
*/
|
|
52
51
|
|
|
53
52
|
result.resmatch = resmatch
|
|
54
53
|
|
|
@@ -30,9 +30,15 @@ function prepareAuth(ctx: Context): Spec | Error {
|
|
|
30
30
|
|
|
31
31
|
const options = client.options()
|
|
32
32
|
|
|
33
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
34
|
+
if (null == options.auth) {
|
|
35
|
+
delprop(headers, HEADER_auth)
|
|
36
|
+
return spec
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
const apikey = getprop(options, OPTION_apikey, NOTFOUND)
|
|
34
40
|
|
|
35
|
-
if (NOTFOUND === apikey) {
|
|
41
|
+
if (NOTFOUND === apikey || null == apikey || '' === apikey) {
|
|
36
42
|
delprop(headers, HEADER_auth)
|
|
37
43
|
}
|
|
38
44
|
else {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"_doc": "Per-SDK test control. Lists tests/operations to skip in unit and live modes; tunes per-test pacing. Edit by hand. Loaded by ts/test/utility.ts.",
|
|
4
|
+
"test": {
|
|
5
|
+
"skip": {
|
|
6
|
+
"live": {
|
|
7
|
+
"direct": [],
|
|
8
|
+
"entityOp": []
|
|
9
|
+
},
|
|
10
|
+
"unit": {
|
|
11
|
+
"direct": [],
|
|
12
|
+
"entityOp": []
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"live": {
|
|
16
|
+
"delayMs": 500
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utility functions for unit tests
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This module provides common helper functions used across unit tests
|
|
5
5
|
* for creating test data, transformations, validations, and environment overrides.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import * as Fs from 'node:fs'
|
|
9
|
+
import * as Path from 'node:path'
|
|
10
|
+
|
|
11
|
+
|
|
8
12
|
// Creates a new step data structure within the data model
|
|
9
13
|
function makeStepData(dm: Record<string, any>, stepname: string): Record<string, any> {
|
|
10
14
|
dm.s[stepname] = {
|
|
@@ -76,11 +80,125 @@ function envOverride(m: Record<string, any>) {
|
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
|
|
83
|
+
// Loads sdk-test-control.json (cached). Returns an empty-skip object if
|
|
84
|
+
// the file is missing or unparsable so tests never crash on a bad config.
|
|
85
|
+
type TestControl = {
|
|
86
|
+
version?: number
|
|
87
|
+
test?: {
|
|
88
|
+
skip?: {
|
|
89
|
+
live?: { direct?: any[], entityOp?: any[] }
|
|
90
|
+
unit?: { direct?: any[], entityOp?: any[] }
|
|
91
|
+
}
|
|
92
|
+
live?: { delayMs?: number }
|
|
93
|
+
[k: string]: any
|
|
94
|
+
}
|
|
95
|
+
[k: string]: any
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let _testControlCache: TestControl | null = null
|
|
99
|
+
|
|
100
|
+
function loadTestControl(): TestControl {
|
|
101
|
+
if (_testControlCache) return _testControlCache
|
|
102
|
+
const ctrlPath = Path.resolve(__dirname, '../test/sdk-test-control.json')
|
|
103
|
+
try {
|
|
104
|
+
_testControlCache = JSON.parse(Fs.readFileSync(ctrlPath, 'utf8')) as TestControl
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
_testControlCache = {
|
|
108
|
+
version: 1,
|
|
109
|
+
test: { skip: { live: { direct: [], entityOp: [] }, unit: { direct: [], entityOp: [] } } }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return _testControlCache!
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
// Returns the skip decision for a given test name from sdk-test-control.json.
|
|
117
|
+
// `kind` is 'direct' (matches by `test` field) or 'entityOp' (matches by
|
|
118
|
+
// `entity` + `op`). `mode` is 'live' or 'unit'.
|
|
119
|
+
function isControlSkipped(
|
|
120
|
+
kind: 'direct' | 'entityOp',
|
|
121
|
+
name: string,
|
|
122
|
+
mode: 'live' | 'unit'
|
|
123
|
+
): { skip: boolean, reason?: string } {
|
|
124
|
+
const ctrl = loadTestControl()
|
|
125
|
+
const list = ctrl?.test?.skip?.[mode]?.[kind] ?? []
|
|
126
|
+
for (const e of list) {
|
|
127
|
+
if (kind === 'direct' && e?.test === name) {
|
|
128
|
+
return { skip: true, reason: e.reason }
|
|
129
|
+
}
|
|
130
|
+
if (kind === 'entityOp') {
|
|
131
|
+
const key = (e?.entity ?? '') + '.' + (e?.op ?? '')
|
|
132
|
+
if (key === name) return { skip: true, reason: e.reason }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { skip: false }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
// Skips the current test if sdk-test-control.json lists it. Returns true
|
|
140
|
+
// when skipped (caller should `return` immediately).
|
|
141
|
+
function maybeSkipControl(
|
|
142
|
+
t: any,
|
|
143
|
+
kind: 'direct' | 'entityOp',
|
|
144
|
+
name: string,
|
|
145
|
+
live: boolean
|
|
146
|
+
): boolean {
|
|
147
|
+
const decision = isControlSkipped(kind, name, live ? 'live' : 'unit')
|
|
148
|
+
if (decision.skip) {
|
|
149
|
+
t.skip(decision.reason || 'skipped via sdk-test-control.json')
|
|
150
|
+
return true
|
|
151
|
+
}
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
// Skips the current live test when required idmap keys aren't supplied.
|
|
157
|
+
// Generated tests call this when they would otherwise pass `undefined`
|
|
158
|
+
// values into a path/query param and 4xx the request.
|
|
159
|
+
function skipIfMissingIds(t: any, setup: any, requiredKeys: string[]): boolean {
|
|
160
|
+
if (!setup.live) return false
|
|
161
|
+
const missing = requiredKeys.filter(k => null == setup.idmap?.[k])
|
|
162
|
+
if (missing.length > 0) {
|
|
163
|
+
t.skip(`live test needs ${missing.join(', ')} via *_ENTID env var (synthetic IDs only)`)
|
|
164
|
+
return true
|
|
165
|
+
}
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
// Per-test live pacing delay (ms). Read from sdk-test-control.json
|
|
171
|
+
// `test.live.delayMs`; defaults to 500ms if absent or invalid.
|
|
172
|
+
function liveDelayMs(): number {
|
|
173
|
+
const ctrl = loadTestControl()
|
|
174
|
+
const v = ctrl?.test?.live?.delayMs
|
|
175
|
+
return ('number' === typeof v && v >= 0) ? v : 500
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// afterEach hook helper for live pacing. Generated tests register this
|
|
180
|
+
// via `afterEach(liveDelay(<envVar>))`; it sleeps `liveDelayMs()` only
|
|
181
|
+
// when the SDK's *_TEST_LIVE env var is set.
|
|
182
|
+
function liveDelay(liveEnvVar: string): () => Promise<void> {
|
|
183
|
+
return async () => {
|
|
184
|
+
if ('TRUE' === process.env[liveEnvVar]) {
|
|
185
|
+
await new Promise(r => setTimeout(r, liveDelayMs()))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
79
191
|
export {
|
|
80
192
|
makeStepData,
|
|
81
193
|
makeMatch,
|
|
82
194
|
makeReqdata,
|
|
83
195
|
makeValid,
|
|
84
196
|
makeCtrl,
|
|
85
|
-
envOverride
|
|
197
|
+
envOverride,
|
|
198
|
+
loadTestControl,
|
|
199
|
+
isControlSkipped,
|
|
200
|
+
maybeSkipControl,
|
|
201
|
+
skipIfMissingIds,
|
|
202
|
+
liveDelayMs,
|
|
203
|
+
liveDelay,
|
|
86
204
|
}
|