@voxgig/sdkgen 4.2.8 → 4.4.0
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/helpers/naming.d.ts +2 -1
- package/dist/helpers/naming.js +50 -12
- package/dist/helpers/naming.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +4 -3
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/project/.sdk/src/cmp/zig/Config_zig.ts +1 -1
- package/project/.sdk/tm/c/tests/primary_corpus_test.c +565 -0
- package/project/.sdk/tm/c/utility/prepare_method.c +3 -1
- package/project/.sdk/tm/c/utility/transform_request.c +4 -3
- package/project/.sdk/tm/clojure/src/sdk/core.clj +55 -5
- package/project/.sdk/tm/clojure/test/sdk/test/primary.clj +171 -1
- package/project/.sdk/tm/clojure/test/sdk/test/struct_corpus.clj +13 -2
- package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +2 -0
- package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +104 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +82 -2
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +50 -2
- package/project/.sdk/tm/elixir/test/primary_utility_test.exs +18 -200
- package/project/.sdk/tm/elixir/test/support/struct_corpus.ex +413 -2
- package/project/.sdk/tm/go/test/custom_utility_test.go +103 -0
- package/project/.sdk/tm/go/test/feature_corpus_test.go +550 -0
- package/project/.sdk/tm/go/utility/make_options.go +7 -1
- package/project/.sdk/tm/go/utility/register.go +194 -0
- package/project/.sdk/tm/java/test/CustomUtilityTest.java +55 -0
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +463 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +11 -2
- package/project/.sdk/tm/java/utility/Register.java +91 -0
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +285 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +56 -2
- package/project/.sdk/tm/lua/utility/make_options.lua +22 -2
- package/project/.sdk/tm/ocaml/Makefile +15 -4
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +8 -1
- package/project/.sdk/tm/ocaml/test/corpus_runner.ml +185 -0
- package/project/.sdk/tm/ocaml/test/primary_utility_test.ml +238 -0
- package/project/.sdk/tm/ocaml/test/struct_corpus.ml +5 -160
- package/project/.sdk/tm/perl/t/feature_corpus.t +345 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +33 -1
- package/project/.sdk/tm/php/core/Context.php +3 -0
- package/project/.sdk/tm/php/core/Control.php +13 -0
- package/project/.sdk/tm/php/core/Error.php +12 -0
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +376 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +30 -1
- package/project/.sdk/tm/py/pkg/utility/make_options.py +42 -1
- package/project/.sdk/tm/py/test/test_feature_corpus.py +309 -0
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +281 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +32 -1
- package/project/.sdk/tm/scala/Makefile +1 -0
- package/project/.sdk/tm/scala/sdktest/PrimaryCorpus.scala +294 -0
- package/project/.sdk/tm/scala/sdktest/StructCorpus.scala +0 -0
- package/project/.sdk/tm/scala/utility/Make.scala +49 -2
- package/project/.sdk/tm/scala/utility/Prepare.scala +3 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +13 -2
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +287 -0
- package/project/.sdk/tm/zig/core/utility.zig +8 -1
- package/project/.sdk/tm/zig/test/primary_utility_test.zig +445 -0
- package/project/.sdk/tm/zig/test/struct_runner.zig +238 -0
- package/project/sdkgen-package.json +1 -1
- package/src/helpers/naming.ts +54 -12
- package/src/sdkgen.ts +2 -1
|
@@ -1,211 +1,29 @@
|
|
|
1
1
|
# ProjectName SDK primary-utility test
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# Drives the SHARED language-neutral corpus (.sdk/test/test.json -> "primary")
|
|
4
|
+
# through this SDK's request-shaping utilities, so the cases cannot drift from
|
|
5
|
+
# the reference implementation. Each section is looked up with getSpec and
|
|
6
|
+
# executed with runset, as the ts/js reference harness does.
|
|
7
|
+
#
|
|
8
|
+
# This suite used to MIRROR the corpus by hand: it asserted a handful of
|
|
9
|
+
# behaviours it had transcribed, so a corpus case that changed — or one that
|
|
10
|
+
# was added — went unnoticed here. Driving the corpus is what makes elixir a
|
|
11
|
+
# FULL parity target rather than a mirrored one.
|
|
5
12
|
|
|
6
13
|
defmodule ProjectName.PrimaryUtilityTest do
|
|
7
14
|
use ExUnit.Case
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
alias ProjectName.{Context, Utility, Spec, Result}
|
|
12
|
-
|
|
13
|
-
defp client, do: ProjectName.test()
|
|
14
|
-
|
|
15
|
-
defp ctx(client, opname \\ "load") do
|
|
16
|
-
Context.new(S.jm(["opname", opname]), ProjectName.get_root_ctx(client))
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
test "make_url substitutes path params and appends query" do
|
|
20
|
-
c = client()
|
|
21
|
-
ctx = ctx(c)
|
|
22
|
-
|
|
23
|
-
S.setprop(ctx, "spec",
|
|
24
|
-
Spec.new(H.deep(%{
|
|
25
|
-
"base" => "http://h",
|
|
26
|
-
"path" => "planet/{id}",
|
|
27
|
-
"params" => %{"id" => "P1"},
|
|
28
|
-
"query" => %{"q" => "x"}
|
|
29
|
-
})))
|
|
30
|
-
|
|
31
|
-
S.setprop(ctx, "result", Result.new(nil))
|
|
32
|
-
|
|
33
|
-
{url, err} = Utility.make_url(ctx)
|
|
34
|
-
assert err == nil
|
|
35
|
-
assert String.contains?(url, "planet/P1")
|
|
36
|
-
assert String.contains?(url, "q=x")
|
|
37
|
-
assert S.getprop(S.getprop(ctx, "result"), "resmatch") != nil
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
test "param resolves reqmatch over match, then data" do
|
|
41
|
-
c = client()
|
|
42
|
-
ctx = ctx(c)
|
|
43
|
-
S.setprop(ctx, "reqmatch", H.deep(%{"id" => "R1"}))
|
|
44
|
-
S.setprop(ctx, "match", H.deep(%{"id" => "M1", "k" => "mk"}))
|
|
45
|
-
S.setprop(ctx, "reqdata", H.deep(%{"d" => "rd"}))
|
|
46
|
-
S.setprop(ctx, "data", H.deep(%{"d" => "dd", "only" => "od"}))
|
|
47
|
-
|
|
48
|
-
assert Utility.param(ctx, "id") == "R1"
|
|
49
|
-
assert Utility.param(ctx, "k") == "mk"
|
|
50
|
-
assert Utility.param(ctx, "d") == "rd"
|
|
51
|
-
assert Utility.param(ctx, "only") == "od"
|
|
52
|
-
assert Utility.param(ctx, "missing") == nil
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
test "prepare_method maps op names to HTTP verbs" do
|
|
56
|
-
c = client()
|
|
57
|
-
assert Utility.prepare_method(ctx(c, "create")) == "POST"
|
|
58
|
-
assert Utility.prepare_method(ctx(c, "update")) == "PUT"
|
|
59
|
-
assert Utility.prepare_method(ctx(c, "load")) == "GET"
|
|
60
|
-
assert Utility.prepare_method(ctx(c, "list")) == "GET"
|
|
61
|
-
assert Utility.prepare_method(ctx(c, "remove")) == "DELETE"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
test "prepare_query keeps reqmatch keys not declared as point params" do
|
|
65
|
-
c = client()
|
|
66
|
-
ctx = ctx(c)
|
|
67
|
-
S.setprop(ctx, "point", H.deep(%{"params" => ["id"]}))
|
|
68
|
-
S.setprop(ctx, "reqmatch", H.deep(%{"id" => "x", "extra" => "y"}))
|
|
69
|
-
q = Utility.prepare_query(ctx)
|
|
70
|
-
assert S.getprop(q, "extra") == "y"
|
|
71
|
-
assert S.getprop(q, "id") == nil
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
test "prepare_params resolves declared params" do
|
|
75
|
-
c = client()
|
|
76
|
-
ctx = ctx(c)
|
|
77
|
-
S.setprop(ctx, "point", H.deep(%{"args" => %{"params" => [%{"name" => "id"}]}}))
|
|
78
|
-
S.setprop(ctx, "reqmatch", H.deep(%{"id" => "P9"}))
|
|
79
|
-
p = Utility.prepare_params(ctx)
|
|
80
|
-
assert S.getprop(p, "id") == "P9"
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
test "prepare_auth sets an authorization header when an apikey is present" do
|
|
84
|
-
c = ProjectName.new(H.deep(%{"apikey" => "secret"}))
|
|
85
|
-
ctx = ctx(c)
|
|
86
|
-
spec = Spec.new(H.deep(%{"headers" => %{}}))
|
|
87
|
-
S.setprop(ctx, "spec", spec)
|
|
88
|
-
{_spec, err} = Utility.prepare_auth(ctx)
|
|
89
|
-
assert err == nil
|
|
90
|
-
# The apikey must reach the authorization header; the exact value carries
|
|
91
|
-
# the API's configured auth prefix (e.g. "Basic secret" for HTTP basic, or
|
|
92
|
-
# just "secret" for an empty prefix), so assert on the apikey being present
|
|
93
|
-
# rather than an exact string that varies per API.
|
|
94
|
-
header = S.getprop(S.getprop(spec, "headers"), "authorization")
|
|
95
|
-
assert header != nil
|
|
96
|
-
assert String.contains?(header, "secret")
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
test "prepare_auth omits the header when no apikey" do
|
|
100
|
-
c = ProjectName.new()
|
|
101
|
-
ctx = ctx(c)
|
|
102
|
-
spec = Spec.new(H.deep(%{"headers" => %{"authorization" => "stale"}}))
|
|
103
|
-
S.setprop(ctx, "spec", spec)
|
|
104
|
-
{_spec, err} = Utility.prepare_auth(ctx)
|
|
105
|
-
assert err == nil
|
|
106
|
-
assert S.getprop(S.getprop(spec, "headers"), "authorization") == nil
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
test "make_options derives a clean key regex" do
|
|
110
|
-
c = ProjectName.test()
|
|
111
|
-
opts = ProjectName.options_map(c)
|
|
112
|
-
assert S.getpath(opts, "__derived__.clean.keyre") == "key|token|id"
|
|
113
|
-
end
|
|
16
|
+
test "primary utilities pass the shared corpus" do
|
|
17
|
+
{pass, fail, failures} = ProjectName.StructCorpus.run_primary()
|
|
114
18
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# __derived__.featureorder. Driven with a synthetic ctx (empty config
|
|
119
|
-
# options) so the assertions do not depend on this SDK's own features.
|
|
120
|
-
|
|
121
|
-
defp resolve_order(feature) do
|
|
122
|
-
ctx =
|
|
123
|
-
S.jm([
|
|
124
|
-
"utility", Utility.new(),
|
|
125
|
-
"options", H.deep(%{"feature" => feature}),
|
|
126
|
-
"config", H.deep(%{"options" => %{}})
|
|
127
|
-
])
|
|
128
|
-
|
|
129
|
-
fo = S.getpath(Utility.make_options(ctx), "__derived__.featureorder")
|
|
130
|
-
n = S.size(fo)
|
|
131
|
-
order = if n == 0, do: [], else: Enum.map(0..(n - 1), fn i -> S.getelem(fo, i) end)
|
|
132
|
-
Enum.join(order, ",")
|
|
133
|
-
end
|
|
19
|
+
if fail > 0 do
|
|
20
|
+
IO.puts("\n" <> Enum.join(Enum.take(failures, 40), "\n"))
|
|
21
|
+
end
|
|
134
22
|
|
|
135
|
-
|
|
136
|
-
assert "test,metrics" ==
|
|
137
|
-
resolve_order(%{"metrics" => %{"active" => true}, "test" => %{"active" => true}})
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
test "feature order: an explicit list preserves its order" do
|
|
141
|
-
assert "metrics,test" ==
|
|
142
|
-
resolve_order([
|
|
143
|
-
%{"name" => "metrics", "active" => true},
|
|
144
|
-
%{"name" => "test", "active" => true}
|
|
145
|
-
])
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Station special case, mirroring test's: its transport wrap must sit
|
|
149
|
-
# immediately outside the base transport, so map-form activation hoists
|
|
150
|
-
# it to just after test - or first, when no test entry exists.
|
|
151
|
-
test "feature order: map form hoists station after test" do
|
|
152
|
-
assert "test,station,metrics" ==
|
|
153
|
-
resolve_order(%{
|
|
154
|
-
"metrics" => %{"active" => true},
|
|
155
|
-
"station" => %{"active" => true},
|
|
156
|
-
"test" => %{"active" => true}
|
|
157
|
-
})
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
test "feature order: station first when no test entry exists" do
|
|
161
|
-
assert "station,metrics" ==
|
|
162
|
-
resolve_order(%{
|
|
163
|
-
"metrics" => %{"active" => true},
|
|
164
|
-
"station" => %{"active" => true}
|
|
165
|
-
})
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# The extend seam (feature INSTANCES supplied at construction - the
|
|
169
|
-
# station adopt path): validate must accept the key verbatim, or the
|
|
170
|
-
# constructor's extend loop reads a stripped option and the seam is dead.
|
|
171
|
-
test "make_options preserves options.extend through validation" do
|
|
172
|
-
marker = S.jm(["name", "station"])
|
|
173
|
-
|
|
174
|
-
ctx =
|
|
175
|
-
S.jm([
|
|
176
|
-
"utility", Utility.new(),
|
|
177
|
-
"options", S.jm(["extend", S.jt([marker])]),
|
|
178
|
-
"config", H.deep(%{"options" => %{}})
|
|
179
|
-
])
|
|
180
|
-
|
|
181
|
-
extend = S.getprop(Utility.make_options(ctx), "extend")
|
|
182
|
-
assert S.islist(extend)
|
|
183
|
-
assert S.size(extend) == 1
|
|
184
|
-
assert S.getprop(S.getelem(extend, 0), "name") == "station"
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
test "make_error formats a namespaced message and raises by default" do
|
|
188
|
-
c = client()
|
|
189
|
-
ctx = ctx(c, "load")
|
|
190
|
-
|
|
191
|
-
err =
|
|
192
|
-
assert_raise ProjectName.Error, fn ->
|
|
193
|
-
Utility.make_error(ctx, Context.make_error(ctx, "boom", "kaboom"))
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
assert String.contains?(err.msg, "load")
|
|
197
|
-
assert String.contains?(err.msg, "kaboom")
|
|
198
|
-
assert err.code == "boom"
|
|
199
|
-
end
|
|
23
|
+
IO.puts("\nPRIMARY CORPUS: PASS #{pass} FAIL #{fail}")
|
|
200
24
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
S.setprop(S.getprop(ctx, "ctrl"), "throw_err", false)
|
|
205
|
-
result = Result.new(H.deep(%{"resdata" => %{"x" => 1}}))
|
|
206
|
-
S.setprop(ctx, "result", result)
|
|
207
|
-
out = Utility.make_error(ctx, Context.make_error(ctx, "boom", "kaboom"))
|
|
208
|
-
assert S.ismap(out)
|
|
209
|
-
assert S.getprop(out, "x") == 1
|
|
25
|
+
# A run that executes nothing is not a pass. The corpus is the point.
|
|
26
|
+
assert pass > 0, "the primary corpus executed no cases"
|
|
27
|
+
assert fail == 0
|
|
210
28
|
end
|
|
211
29
|
end
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
defmodule ProjectName.StructCorpus do
|
|
9
9
|
alias Voxgig.Struct, as: S
|
|
10
10
|
alias Voxgig.Struct.Error, as: SE
|
|
11
|
+
alias ProjectName.Utility, as: U
|
|
11
12
|
|
|
12
13
|
@nullmark "__NULL__"
|
|
13
14
|
@undefmark "__UNDEF__"
|
|
@@ -263,7 +264,23 @@ defmodule ProjectName.StructCorpus do
|
|
|
263
264
|
defp resolve_args(entry) do
|
|
264
265
|
cond do
|
|
265
266
|
ehas(entry, "ctx") -> [eget(entry, "ctx")]
|
|
266
|
-
ehas(entry, "args") ->
|
|
267
|
+
ehas(entry, "args") ->
|
|
268
|
+
a = eget(entry, "args")
|
|
269
|
+
|
|
270
|
+
if S.islist(a) do
|
|
271
|
+
args = velems(a)
|
|
272
|
+
# ts's resolveArgs writes the live first arg back as entry.ctx, so a
|
|
273
|
+
# `match: {ctx: ...}` resolves for args-style entries too. Without it
|
|
274
|
+
# every such assertion reads null and asserts nothing.
|
|
275
|
+
case args do
|
|
276
|
+
[first | _] -> if S.ismap(first), do: S.setprop(entry, "ctx", first)
|
|
277
|
+
_ -> :ok
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
args
|
|
281
|
+
else
|
|
282
|
+
[]
|
|
283
|
+
end
|
|
267
284
|
ehas(entry, "in") -> [S.clone(eget(entry, "in"))]
|
|
268
285
|
true -> []
|
|
269
286
|
end
|
|
@@ -301,7 +318,13 @@ defmodule ProjectName.StructCorpus do
|
|
|
301
318
|
if ehas(entry, "match") do
|
|
302
319
|
do_match(
|
|
303
320
|
eget(entry, "match"),
|
|
304
|
-
|
|
321
|
+
# ts hands do_match the ERROR OBJECT, so a corpus
|
|
322
|
+
# `match: {err: {message: ...}}` resolves; a bare string leaves
|
|
323
|
+
# err.message reading null.
|
|
324
|
+
S.jm([
|
|
325
|
+
"in", eget(entry, "in"), "out", eget(entry, "res"),
|
|
326
|
+
"ctx", eget(entry, "ctx"), "err", S.jm(["message", msg])
|
|
327
|
+
])
|
|
305
328
|
)
|
|
306
329
|
end
|
|
307
330
|
|
|
@@ -674,4 +697,392 @@ defmodule ProjectName.StructCorpus do
|
|
|
674
697
|
|
|
675
698
|
{Process.get(:npass, 0), Process.get(:nfail, 0), Process.get(:failures, [])}
|
|
676
699
|
end
|
|
700
|
+
|
|
701
|
+
# --- PRIMARY corpus ------------------------------------------------------
|
|
702
|
+
#
|
|
703
|
+
# Drives .sdk/test/test.json -> "primary", the same language-neutral corpus
|
|
704
|
+
# the ts/js/lua/lean suites execute, through THIS SDK's request-shaping
|
|
705
|
+
# utilities. Before this, elixir mirrored the corpus by hand, so its cases
|
|
706
|
+
# could drift from the reference without anything noticing.
|
|
707
|
+
#
|
|
708
|
+
# The harness below is run_set's, with one difference: the subject receives
|
|
709
|
+
# the ENTRY rather than resolved args, because the args-style sections have
|
|
710
|
+
# to publish the context they build back onto the entry — a `match: ctx:`
|
|
711
|
+
# assertion reads it from there.
|
|
712
|
+
|
|
713
|
+
# A struct VALUE is itself a 2-tuple — {:vmap, id}, {:vlist, id}, {:vinj, id}
|
|
714
|
+
# — so a bare `{res, err}` clause destructures a perfectly good map into
|
|
715
|
+
# res=:vmap, err=<heap id>, and the run then fails with the heap id as its
|
|
716
|
+
# error message. Match the value shapes first.
|
|
717
|
+
defp pnorm({:vmap, _} = v), do: {v, nil}
|
|
718
|
+
defp pnorm({:vlist, _} = v), do: {v, nil}
|
|
719
|
+
defp pnorm({:vinj, _} = v), do: {v, nil}
|
|
720
|
+
defp pnorm({res, err}), do: {res, err}
|
|
721
|
+
defp pnorm(res), do: {res, nil}
|
|
722
|
+
|
|
723
|
+
# A ctx for a ctx-style entry: the corpus supplies it, the client supplies
|
|
724
|
+
# what makes it live (utility dispatch, options, config).
|
|
725
|
+
defp pctx(entry, util, opts, config) do
|
|
726
|
+
ctxmap = if ehas(entry, "ctx") and S.ismap(eget(entry, "ctx")), do: eget(entry, "ctx"), else: S.jm([])
|
|
727
|
+
ctx = pbuild(ctxmap, util, opts, config)
|
|
728
|
+
S.setprop(entry, "ctx", ctx)
|
|
729
|
+
ctx
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# A LIVE context from a corpus map. The corpus carries spec/result/response
|
|
733
|
+
# as plain JSON; the utilities read and MUTATE them through Spec/Result/
|
|
734
|
+
# Response, so a bare map leaves them with nothing to work on — every
|
|
735
|
+
# `match: ctx.result.*` assertion then reads null, and prepare_* returns
|
|
736
|
+
# nothing. Mirrors make_ctx_from_map in the lua/js reference drivers.
|
|
737
|
+
defp pbuild(ctxmap, util, opts, config) do
|
|
738
|
+
ctx = ProjectName.Context.new(ctxmap, nil)
|
|
739
|
+
S.setprop(ctx, "utility", util)
|
|
740
|
+
S.setprop(ctx, "config", config)
|
|
741
|
+
# THE CLIENT IS OPT-IN. Some utilities read their defaults off it —
|
|
742
|
+
# prepare_headers reads opts_map(ctx.client) — but the client holds the
|
|
743
|
+
# root ctx, which holds the client, so anything that WALKS a ctx carrying
|
|
744
|
+
# one never terminates: makeContext returns a ctx, and make_error raises an
|
|
745
|
+
# error whose `ctx:` field is the ctx. Both hung the run until ExUnit
|
|
746
|
+
# killed it. Only the sections that need client defaults ask for it.
|
|
747
|
+
if Process.get(:pwantclient) == true and Process.get(:pclient) != nil,
|
|
748
|
+
do: S.setprop(ctx, "client", Process.get(:pclient))
|
|
749
|
+
if S.getprop(ctx, "options") == nil, do: S.setprop(ctx, "options", opts)
|
|
750
|
+
|
|
751
|
+
specmap = S.getprop(ctxmap, "spec")
|
|
752
|
+
if S.ismap(specmap), do: S.setprop(ctx, "spec", ProjectName.Spec.new(specmap))
|
|
753
|
+
|
|
754
|
+
resmap = S.getprop(ctxmap, "result")
|
|
755
|
+
|
|
756
|
+
if S.ismap(resmap) do
|
|
757
|
+
result = ProjectName.Result.new(resmap)
|
|
758
|
+
errmap = S.getprop(resmap, "err")
|
|
759
|
+
|
|
760
|
+
if S.ismap(errmap) do
|
|
761
|
+
msg = jss(S.getprop(errmap, "message"))
|
|
762
|
+
if is_binary(msg) and msg != "",
|
|
763
|
+
do: S.setprop(result, "err", ProjectName.Error.new("", msg, nil))
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
S.setprop(ctx, "result", result)
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
respmap = S.getprop(ctxmap, "response")
|
|
770
|
+
|
|
771
|
+
if S.ismap(respmap) do
|
|
772
|
+
response = ProjectName.Response.new(respmap)
|
|
773
|
+
body = S.getprop(respmap, "body")
|
|
774
|
+
if body != nil, do: S.setprop(response, "json_func", fn -> body end)
|
|
775
|
+
|
|
776
|
+
hdrs = S.getprop(respmap, "headers")
|
|
777
|
+
|
|
778
|
+
if S.ismap(hdrs) do
|
|
779
|
+
lower = S.jm([])
|
|
780
|
+
Enum.each(S.keysof(hdrs), fn k ->
|
|
781
|
+
S.setprop(lower, String.downcase(k), S.getprop(hdrs, k))
|
|
782
|
+
end)
|
|
783
|
+
S.setprop(response, "headers", lower)
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
S.setprop(ctx, "response", response)
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
ctx
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
# A ctx from a bare map (args-style sections), published back onto the entry.
|
|
793
|
+
defp pmapctx(entry, v, util, opts, config) do
|
|
794
|
+
ctx = pbuild(if(S.ismap(v), do: v, else: S.jm([])), util, opts, config)
|
|
795
|
+
S.setprop(entry, "ctx", ctx)
|
|
796
|
+
ctx
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
# A ctx that carries the client, for the sections whose utilities read their
|
|
800
|
+
# defaults off it. Scoped so nothing else inherits the cycle.
|
|
801
|
+
defp pctx_client(entry, util, opts, config) do
|
|
802
|
+
Process.put(:pwantclient, true)
|
|
803
|
+
ctx = pctx(entry, util, opts, config)
|
|
804
|
+
Process.put(:pwantclient, false)
|
|
805
|
+
ctx
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
defp parg(entry, i) do
|
|
809
|
+
a = eget(entry, "args")
|
|
810
|
+
if S.islist(a), do: Enum.at(velems(a), i), else: nil
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
defp run_primary_set(group, node, subject) do
|
|
814
|
+
if System.get_env("VOX_DBG"), do: IO.puts("SECTION #{group}")
|
|
815
|
+
fixed = fixj(S.clone(node), true)
|
|
816
|
+
testset = S.getprop(fixed, "set")
|
|
817
|
+
|
|
818
|
+
if S.islist(testset) do
|
|
819
|
+
Enum.each(velems(testset), fn entry ->
|
|
820
|
+
name = jss(eget(entry, "name"))
|
|
821
|
+
|
|
822
|
+
try do
|
|
823
|
+
if not ehas(entry, "out"), do: S.setprop(entry, "out", @nullmark)
|
|
824
|
+
{res0, err} = pnorm(subject.(entry))
|
|
825
|
+
|
|
826
|
+
if err != nil do
|
|
827
|
+
raise SE, message: perrmsg(err)
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
# The RESULT can carry them too — makeContext returns a context — and
|
|
831
|
+
# fixj walks it before anything else gets the chance to strip.
|
|
832
|
+
if S.ismap(res0) do
|
|
833
|
+
S.delprop(res0, "client")
|
|
834
|
+
S.delprop(res0, "utility")
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
res = fixj(res0, true)
|
|
838
|
+
S.setprop(entry, "res", res)
|
|
839
|
+
|
|
840
|
+
# STRIP THE LIVE REFERENCES BEFORE MATCHING. check_result matches
|
|
841
|
+
# against entry.ctx, and the ctx carries the client so utilities like
|
|
842
|
+
# prepare_headers can read their defaults off it. The client holds the
|
|
843
|
+
# root ctx, which holds the client — walking that is a cycle, and the
|
|
844
|
+
# run hangs until ExUnit kills it at 60s. The utilities have already
|
|
845
|
+
# run by here, so nothing needs them any more.
|
|
846
|
+
ectx = eget(entry, "ctx")
|
|
847
|
+
|
|
848
|
+
if S.ismap(ectx) do
|
|
849
|
+
S.delprop(ectx, "client")
|
|
850
|
+
S.delprop(ectx, "utility")
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
check_result(entry, resolve_args(entry), res)
|
|
854
|
+
record(group, name, true, "")
|
|
855
|
+
rescue
|
|
856
|
+
e ->
|
|
857
|
+
try do
|
|
858
|
+
handle_error(entry, e)
|
|
859
|
+
record(group, name, true, "")
|
|
860
|
+
rescue
|
|
861
|
+
e2 -> record(group, name, false, errmsg(e2))
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
end)
|
|
865
|
+
end
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
# The corpus speaks camelCase; snake_case languages do not. lua, py and
|
|
869
|
+
# elixir all store `status_text` internally while reading `statusText` off
|
|
870
|
+
# the wire, so a `match: ctx.result.statusText` assertion reads null unless
|
|
871
|
+
# the driver publishes a neutral-named view back. The lua reference driver
|
|
872
|
+
# does exactly this.
|
|
873
|
+
defp pneutral_result(result) do
|
|
874
|
+
if S.ismap(result) do
|
|
875
|
+
err = S.getprop(result, "err")
|
|
876
|
+
|
|
877
|
+
S.jm([
|
|
878
|
+
"ok", S.getprop(result, "ok"),
|
|
879
|
+
"status", S.getprop(result, "status"),
|
|
880
|
+
"statusText", S.getprop(result, "status_text"),
|
|
881
|
+
"headers", S.getprop(result, "headers"),
|
|
882
|
+
"body", S.getprop(result, "body"),
|
|
883
|
+
"err", if(err == nil, do: nil, else: S.jm(["message", perrmsg(err)]))
|
|
884
|
+
])
|
|
885
|
+
end
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
# Publish the neutral view of ctx.result onto the entry's ctx, so
|
|
889
|
+
# `match: ctx.result.*` sees corpus-shaped data.
|
|
890
|
+
defp ppub(entry, ctx) do
|
|
891
|
+
ectx = eget(entry, "ctx")
|
|
892
|
+
result = if S.ismap(ctx), do: S.getprop(ctx, "result")
|
|
893
|
+
if S.ismap(ectx) and S.ismap(result) do
|
|
894
|
+
S.setprop(ectx, "result", pneutral_result(result))
|
|
895
|
+
end
|
|
896
|
+
:ok
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
defp perrmsg(err) do
|
|
900
|
+
cond do
|
|
901
|
+
is_binary(err) -> err
|
|
902
|
+
S.ismap(err) -> jss(S.getprop(err, "message")) || S.stringify(err)
|
|
903
|
+
true -> inspect(err)
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
defp psec(primary, name, sub \\ "basic"), do: S.getprop(S.getprop(primary, name), sub)
|
|
908
|
+
|
|
909
|
+
# A CLIENT built from a section's DEF.setup.a block.
|
|
910
|
+
#
|
|
911
|
+
# prepare_auth reads opts_map(ctx.client) — as the ts reference does, via
|
|
912
|
+
# client.options() — so a section's setup options have to reach it through a
|
|
913
|
+
# client, not through ctx.options. Passing them as options alone left the
|
|
914
|
+
# apikey unreachable and the authorization header never set.
|
|
915
|
+
defp pclient_for(primary, name) do
|
|
916
|
+
setup = S.getprop(S.getprop(S.getprop(S.getprop(primary, name), "DEF"), "setup"), "a")
|
|
917
|
+
if S.ismap(setup), do: ProjectName.test(nil, S.clone(setup)), else: ProjectName.test()
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
# Options built from a section's DEF.setup.a block, the way the reference
|
|
921
|
+
# harness does. Falls back to the client's options when a section has none.
|
|
922
|
+
defp psetup(primary, name, util, config) do
|
|
923
|
+
setup =
|
|
924
|
+
S.getprop(S.getprop(S.getprop(S.getprop(primary, name), "DEF"), "setup"), "a")
|
|
925
|
+
|
|
926
|
+
ctx = S.jm([])
|
|
927
|
+
S.setprop(ctx, "utility", util)
|
|
928
|
+
S.setprop(ctx, "config", config)
|
|
929
|
+
S.setprop(ctx, "options", if(S.ismap(setup), do: S.clone(setup), else: S.jm([])))
|
|
930
|
+
U.make_options(ctx)
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
def run_primary(testfile \\ nil) do
|
|
934
|
+
Process.put(:npass, 0)
|
|
935
|
+
Process.put(:nfail, 0)
|
|
936
|
+
Process.put(:failures, [])
|
|
937
|
+
|
|
938
|
+
testfile = testfile || Path.join(File.cwd!(), "../.sdk/test/test.json")
|
|
939
|
+
root = parse(File.read!(testfile))
|
|
940
|
+
primary = S.getprop(root, "primary")
|
|
941
|
+
|
|
942
|
+
client = ProjectName.test()
|
|
943
|
+
Process.put(:pclient, client)
|
|
944
|
+
Process.put(:pbaseclient, client)
|
|
945
|
+
rootctx = ProjectName.get_root_ctx(client)
|
|
946
|
+
util = S.getprop(rootctx, "utility")
|
|
947
|
+
config = S.getprop(rootctx, "config")
|
|
948
|
+
opts = S.getprop(rootctx, "options")
|
|
949
|
+
|
|
950
|
+
run_primary_set("done", psec(primary, "done"), fn e ->
|
|
951
|
+
U.done(pctx(e, util, opts, config))
|
|
952
|
+
end)
|
|
953
|
+
|
|
954
|
+
# makeContext takes a PLAIN map and returns a context; it needs no client
|
|
955
|
+
# and no utility dispatch. Handing it a live ctx made Context.new copy the
|
|
956
|
+
# client through, and fixj then walked client -> rootctx -> client.
|
|
957
|
+
run_primary_set("makeContext", psec(primary, "makeContext"), fn e ->
|
|
958
|
+
inv = eget(e, "in")
|
|
959
|
+
ctxmap = if S.ismap(inv), do: inv, else: S.jm([])
|
|
960
|
+
S.setprop(e, "ctx", ctxmap)
|
|
961
|
+
ProjectName.Context.new(ctxmap, nil)
|
|
962
|
+
end)
|
|
963
|
+
|
|
964
|
+
run_primary_set("makeError", psec(primary, "makeError"), fn e ->
|
|
965
|
+
ctx = pmapctx(e, parg(e, 0), util, opts, config)
|
|
966
|
+
{nil, U.make_error(ctx, parg(e, 1))}
|
|
967
|
+
end)
|
|
968
|
+
|
|
969
|
+
run_primary_set("makeOptions", psec(primary, "makeOptions"), fn e ->
|
|
970
|
+
inv = eget(e, "in")
|
|
971
|
+
ctx = pmapctx(e, S.jm([]), util, opts, config)
|
|
972
|
+
S.setprop(ctx, "config", S.getprop(inv, "config"))
|
|
973
|
+
S.setprop(ctx, "options", S.getprop(inv, "options"))
|
|
974
|
+
U.make_options(ctx)
|
|
975
|
+
end)
|
|
976
|
+
|
|
977
|
+
run_primary_set("makeRequest", psec(primary, "makeRequest"), fn e ->
|
|
978
|
+
U.make_request(pctx(e, util, opts, config))
|
|
979
|
+
end)
|
|
980
|
+
|
|
981
|
+
run_primary_set("makeResponse", psec(primary, "makeResponse"), fn e ->
|
|
982
|
+
ctx = pctx(e, util, opts, config)
|
|
983
|
+
out = U.make_response(ctx)
|
|
984
|
+
ppub(e, ctx)
|
|
985
|
+
out
|
|
986
|
+
end)
|
|
987
|
+
|
|
988
|
+
# makeSpec and prepareAuth are configured by their own DEF.setup.a block,
|
|
989
|
+
# not by the client's default options — the corpus supplies the base URL
|
|
990
|
+
# and the api key the cases assert on.
|
|
991
|
+
spec_opts = psetup(primary, "makeSpec", util, config)
|
|
992
|
+
|
|
993
|
+
run_primary_set("makeSpec", psec(primary, "makeSpec"), fn e ->
|
|
994
|
+
U.make_spec(pctx_client(e, util, spec_opts, config))
|
|
995
|
+
end)
|
|
996
|
+
|
|
997
|
+
run_primary_set("makeUrl", psec(primary, "makeUrl"), fn e ->
|
|
998
|
+
U.make_url(pctx(e, util, opts, config))
|
|
999
|
+
end)
|
|
1000
|
+
|
|
1001
|
+
run_primary_set("operator", psec(primary, "operator"), fn e ->
|
|
1002
|
+
inv = eget(e, "in")
|
|
1003
|
+
op = ProjectName.Operation.new(if(S.ismap(inv), do: inv, else: S.jm([])))
|
|
1004
|
+
S.jm([
|
|
1005
|
+
"entity", S.getprop(op, "entity"),
|
|
1006
|
+
"input", S.getprop(op, "input"),
|
|
1007
|
+
"name", S.getprop(op, "name"),
|
|
1008
|
+
"points", S.getprop(op, "points")
|
|
1009
|
+
])
|
|
1010
|
+
end)
|
|
1011
|
+
|
|
1012
|
+
run_primary_set("param", psec(primary, "param"), fn e ->
|
|
1013
|
+
ctx = pmapctx(e, parg(e, 0), util, opts, config)
|
|
1014
|
+
U.param(ctx, parg(e, 1))
|
|
1015
|
+
end)
|
|
1016
|
+
|
|
1017
|
+
auth_opts = psetup(primary, "prepareAuth", util, config)
|
|
1018
|
+
auth_client = pclient_for(primary, "prepareAuth")
|
|
1019
|
+
|
|
1020
|
+
run_primary_set("prepareAuth", psec(primary, "prepareAuth"), fn e ->
|
|
1021
|
+
Process.put(:pclient, auth_client)
|
|
1022
|
+
out = U.prepare_auth(pctx_client(e, util, auth_opts, config))
|
|
1023
|
+
Process.put(:pclient, Process.get(:pbaseclient))
|
|
1024
|
+
out
|
|
1025
|
+
end)
|
|
1026
|
+
|
|
1027
|
+
run_primary_set("prepareBody", psec(primary, "prepareBody"), fn e ->
|
|
1028
|
+
U.prepare_body(pctx(e, util, opts, config))
|
|
1029
|
+
end)
|
|
1030
|
+
|
|
1031
|
+
run_primary_set("prepareHeaders", psec(primary, "prepareHeaders"), fn e ->
|
|
1032
|
+
U.prepare_headers(pctx_client(e, util, opts, config))
|
|
1033
|
+
end)
|
|
1034
|
+
|
|
1035
|
+
run_primary_set("prepareMethod", psec(primary, "prepareMethod"), fn e ->
|
|
1036
|
+
U.prepare_method(pctx(e, util, opts, config))
|
|
1037
|
+
end)
|
|
1038
|
+
|
|
1039
|
+
run_primary_set("prepareParams", psec(primary, "prepareParams"), fn e ->
|
|
1040
|
+
U.prepare_params(pctx(e, util, opts, config))
|
|
1041
|
+
end)
|
|
1042
|
+
|
|
1043
|
+
run_primary_set("preparePath", psec(primary, "preparePath"), fn e ->
|
|
1044
|
+
U.prepare_path(pctx(e, util, opts, config))
|
|
1045
|
+
end)
|
|
1046
|
+
|
|
1047
|
+
run_primary_set("prepareQuery", psec(primary, "prepareQuery"), fn e ->
|
|
1048
|
+
U.prepare_query(pctx(e, util, opts, config))
|
|
1049
|
+
end)
|
|
1050
|
+
|
|
1051
|
+
run_primary_set("resultBasic", psec(primary, "resultBasic"), fn e ->
|
|
1052
|
+
ctx = pctx(e, util, opts, config)
|
|
1053
|
+
out = U.result_basic(ctx)
|
|
1054
|
+
ppub(e, ctx)
|
|
1055
|
+
if S.ismap(out) and S.getprop(out, "status_text") != nil,
|
|
1056
|
+
do: pneutral_result(out),
|
|
1057
|
+
else: out
|
|
1058
|
+
end)
|
|
1059
|
+
|
|
1060
|
+
run_primary_set("resultBody", psec(primary, "resultBody"), fn e ->
|
|
1061
|
+
ctx = pctx(e, util, opts, config)
|
|
1062
|
+
out = U.result_body(ctx)
|
|
1063
|
+
ppub(e, ctx)
|
|
1064
|
+
if S.ismap(out) and S.getprop(out, "status_text") != nil,
|
|
1065
|
+
do: pneutral_result(out),
|
|
1066
|
+
else: out
|
|
1067
|
+
end)
|
|
1068
|
+
|
|
1069
|
+
run_primary_set("resultHeaders", psec(primary, "resultHeaders"), fn e ->
|
|
1070
|
+
ctx = pctx(e, util, opts, config)
|
|
1071
|
+
out = U.result_headers(ctx)
|
|
1072
|
+
ppub(e, ctx)
|
|
1073
|
+
if S.ismap(out) and S.getprop(out, "status_text") != nil,
|
|
1074
|
+
do: pneutral_result(out),
|
|
1075
|
+
else: out
|
|
1076
|
+
end)
|
|
1077
|
+
|
|
1078
|
+
run_primary_set("transformRequest", psec(primary, "transformRequest"), fn e ->
|
|
1079
|
+
U.transform_request(pctx(e, util, opts, config))
|
|
1080
|
+
end)
|
|
1081
|
+
|
|
1082
|
+
run_primary_set("transformResponse", psec(primary, "transformResponse"), fn e ->
|
|
1083
|
+
U.transform_response(pctx(e, util, opts, config))
|
|
1084
|
+
end)
|
|
1085
|
+
|
|
1086
|
+
{Process.get(:npass, 0), Process.get(:nfail, 0), Process.get(:failures, [])}
|
|
1087
|
+
end
|
|
677
1088
|
end
|