@voxgig/sdkgen 4.4.1 → 4.5.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/FeatureDocs.d.ts +16 -0
- package/dist/cmp/FeatureDocs.js +72 -0
- package/dist/cmp/FeatureDocs.js.map +1 -0
- package/dist/cmp/Readme.js +2 -0
- package/dist/cmp/Readme.js.map +1 -1
- package/dist/cmp/ReadmeFeatures.d.ts +2 -0
- package/dist/cmp/ReadmeFeatures.js +95 -0
- package/dist/cmp/ReadmeFeatures.js.map +1 -0
- package/dist/cmp/ReadmeRef.js +4 -0
- package/dist/cmp/ReadmeRef.js.map +1 -1
- package/dist/cmp/ReadmeRefFeatures.d.ts +2 -0
- package/dist/cmp/ReadmeRefFeatures.js +130 -0
- package/dist/cmp/ReadmeRefFeatures.js.map +1 -0
- package/dist/cmp/ReadmeTop.js +46 -0
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/opShape.js +5 -1
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +30 -10
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +17 -1
- package/dist/utility.js.map +1 -1
- package/package.json +1 -1
- package/project/.sdk/src/cmp/c/ReadmeRef_c.ts +5 -0
- package/project/.sdk/src/cmp/clojure/ReadmeRef_clojure.ts +5 -0
- package/project/.sdk/src/cmp/cpp/ReadmeRef_cpp.ts +5 -0
- package/project/.sdk/src/cmp/csharp/ReadmeRef_csharp.ts +5 -0
- package/project/.sdk/src/cmp/dart/ReadmeRef_dart.ts +5 -0
- package/project/.sdk/src/cmp/elixir/ReadmeRef_elixir.ts +5 -0
- package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +5 -0
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +123 -2
- package/project/.sdk/src/cmp/java/ReadmeRef_java.ts +5 -0
- package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +5 -0
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +62 -0
- package/project/.sdk/src/cmp/kotlin/ReadmeRef_kotlin.ts +5 -0
- package/project/.sdk/src/cmp/lean/ReadmeRef_lean.ts +27 -3
- package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +5 -0
- package/project/.sdk/src/cmp/ocaml/ReadmeRef_ocaml.ts +5 -0
- package/project/.sdk/src/cmp/perl/ReadmeRef_perl.ts +5 -0
- package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +5 -0
- package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +5 -0
- package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +5 -0
- package/project/.sdk/src/cmp/rust/ReadmeRef_rust.ts +5 -0
- package/project/.sdk/src/cmp/scala/ReadmeRef_scala.ts +5 -0
- package/project/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +19 -1
- package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +27 -2
- package/project/.sdk/src/cmp/swift/ReadmeRef_swift.ts +5 -0
- package/project/.sdk/src/cmp/ts/Config_ts.ts +4 -1
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +9 -4
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +8 -3
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +3 -2
- package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +7 -2
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +3 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +5 -2
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +5 -2
- package/project/.sdk/src/cmp/zig/ReadmeRef_zig.ts +5 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +6 -2
- package/project/.sdk/tm/clojure/src/sdk/features.clj +175 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +5 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +4 -0
- package/project/.sdk/tm/dart/test/feature_test.dart +54 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +4 -1
- package/project/.sdk/tm/go/feature/test_feature.go +17 -6
- package/project/.sdk/tm/go/test/feature_corpus_test.go +10 -3
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +5 -4
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +8 -1
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +3 -1
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +13 -3
- package/project/.sdk/tm/js/test/feature.test.js +19 -9
- package/project/.sdk/tm/lean/src/SdkFeatures.lean +186 -1
- package/project/.sdk/tm/lua/feature/test_feature.lua +21 -7
- package/project/.sdk/tm/lua/test/feature_test.lua +18 -18
- package/project/.sdk/tm/lua/utility/make_options.lua +2 -0
- package/project/.sdk/tm/ocaml/sdk_features.ml +5 -0
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +4 -1
- package/project/.sdk/tm/perl/t/feature_corpus.t +5 -4
- package/project/.sdk/tm/perl/utility/make_options.pm +4 -1
- package/project/.sdk/tm/php/feature/TestFeature.php +19 -8
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +7 -4
- package/project/.sdk/tm/php/utility/MakeOptions.php +2 -1
- package/project/.sdk/tm/py/pkg/feature/test_feature.py +15 -4
- package/project/.sdk/tm/py/pkg/utility/make_options.py +2 -0
- package/project/.sdk/tm/py/test/test_feature.py +40 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +14 -3
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -4
- package/project/.sdk/tm/rb/utility/make_options.rb +4 -1
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +15 -1
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +3 -1
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +20 -0
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +13 -3
- package/project/.sdk/tm/ts/test/feature.test.ts +19 -9
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/FeatureDocs.ts +100 -0
- package/src/cmp/Readme.ts +2 -0
- package/src/cmp/ReadmeFeatures.ts +113 -0
- package/src/cmp/ReadmeRef.ts +4 -0
- package/src/cmp/ReadmeRefFeatures.ts +149 -0
- package/src/cmp/ReadmeTop.ts +48 -0
- package/src/helpers/opShape.ts +5 -1
- package/src/sdkgen.ts +28 -7
- package/src/utility.ts +21 -1
|
@@ -441,7 +441,7 @@ describe("feature", function()
|
|
|
441
441
|
-- retry ---------------------------------------------------------------
|
|
442
442
|
if has_feature("retry") then describe("retry", function()
|
|
443
443
|
|
|
444
|
-
it("retries transient failures then succeeds", function()
|
|
444
|
+
if has_feature("netsim") then it("retries transient failures then succeeds", function()
|
|
445
445
|
local clock = make_clock()
|
|
446
446
|
local h = make_client({ features = {
|
|
447
447
|
{ name = "netsim", options = { failTimes = 2, failStatus = 503 } },
|
|
@@ -450,9 +450,9 @@ describe("feature", function()
|
|
|
450
450
|
} })
|
|
451
451
|
assert.is_true(h.op({ op = "load" }).ok)
|
|
452
452
|
assert.are.equal(2, h.client._retry.attempts)
|
|
453
|
-
end)
|
|
453
|
+
end) end
|
|
454
454
|
|
|
455
|
-
it("gives up after the budget", function()
|
|
455
|
+
if has_feature("netsim") then it("gives up after the budget", function()
|
|
456
456
|
local clock = make_clock()
|
|
457
457
|
local h = make_client({ features = {
|
|
458
458
|
{ name = "netsim", options = { failTimes = 9, failStatus = 500 } },
|
|
@@ -460,7 +460,7 @@ describe("feature", function()
|
|
|
460
460
|
retries = 2, minDelay = 1, jitter = false, sleep = clock.sleep } },
|
|
461
461
|
} })
|
|
462
462
|
assert.are.equal(500, h.op({ op = "load" }).result.status)
|
|
463
|
-
end)
|
|
463
|
+
end) end
|
|
464
464
|
|
|
465
465
|
it("does not retry a non-retryable status", function()
|
|
466
466
|
local rec = recording_server(function() return make_response(404) end)
|
|
@@ -487,7 +487,7 @@ describe("feature", function()
|
|
|
487
487
|
assert.are.equal(3, n)
|
|
488
488
|
end)
|
|
489
489
|
|
|
490
|
-
it("honours a server Retry-After", function()
|
|
490
|
+
if has_feature("netsim") then it("honours a server Retry-After", function()
|
|
491
491
|
local clock = make_clock()
|
|
492
492
|
local h = make_client({ features = {
|
|
493
493
|
{ name = "netsim", options = { rateLimitTimes = 1, retryAfter = 2 } },
|
|
@@ -497,15 +497,15 @@ describe("feature", function()
|
|
|
497
497
|
} })
|
|
498
498
|
assert.is_true(h.op({ op = "load" }).ok)
|
|
499
499
|
assert.are.equal(2000, clock.time())
|
|
500
|
-
end)
|
|
500
|
+
end) end
|
|
501
501
|
|
|
502
|
-
it("default jitter path still succeeds", function()
|
|
502
|
+
if has_feature("netsim") then it("default jitter path still succeeds", function()
|
|
503
503
|
local h = make_client({ features = {
|
|
504
504
|
{ name = "netsim", options = { failTimes = 1 } },
|
|
505
505
|
{ name = "retry", options = { retries = 2, minDelay = 0 } },
|
|
506
506
|
} })
|
|
507
507
|
assert.is_true(h.op({ op = "load" }).ok)
|
|
508
|
-
end)
|
|
508
|
+
end) end
|
|
509
509
|
|
|
510
510
|
it("inactive retry does not wrap", function()
|
|
511
511
|
local rec = recording_server(function() return make_response(503) end)
|
|
@@ -551,7 +551,7 @@ describe("feature", function()
|
|
|
551
551
|
-- shared virtual clock stands in for elapsed transport time.
|
|
552
552
|
if has_feature("timeout") then describe("timeout", function()
|
|
553
553
|
|
|
554
|
-
it("a slow request times out", function()
|
|
554
|
+
if has_feature("netsim") then it("a slow request times out", function()
|
|
555
555
|
local clock = make_clock()
|
|
556
556
|
local h = make_client({ features = {
|
|
557
557
|
{ name = "netsim", options = { latency = 80, sleep = clock.sleep } },
|
|
@@ -560,7 +560,7 @@ describe("feature", function()
|
|
|
560
560
|
local res = h.op({ op = "load" })
|
|
561
561
|
assert.are.equal("timeout", res.error.code)
|
|
562
562
|
assert.are.equal(1, h.client._timeout.count)
|
|
563
|
-
end)
|
|
563
|
+
end) end
|
|
564
564
|
|
|
565
565
|
it("a fast request passes through", function()
|
|
566
566
|
local clock = make_clock()
|
|
@@ -819,7 +819,7 @@ describe("feature", function()
|
|
|
819
819
|
-- metrics ---------------------------------------------------------------
|
|
820
820
|
if has_feature("metrics") then describe("metrics", function()
|
|
821
821
|
|
|
822
|
-
it("counts ok and err per op", function()
|
|
822
|
+
if has_feature("netsim") then it("counts ok and err per op", function()
|
|
823
823
|
local h = make_client({ features = {
|
|
824
824
|
{ name = "netsim", options = { failTimes = 1, failStatus = 500 } },
|
|
825
825
|
{ name = "metrics", options = {} },
|
|
@@ -832,7 +832,7 @@ describe("feature", function()
|
|
|
832
832
|
assert.are.equal(2, m.total.ok)
|
|
833
833
|
assert.are.equal(1, m.total.err)
|
|
834
834
|
assert.are.equal(2, m.ops["widget.load"].count)
|
|
835
|
-
end)
|
|
835
|
+
end) end
|
|
836
836
|
|
|
837
837
|
it("injected clock accumulates durations", function()
|
|
838
838
|
local t = 0
|
|
@@ -872,14 +872,14 @@ describe("feature", function()
|
|
|
872
872
|
assert.is_truthy(string.match(sent["traceparent"], "^00%-.+%-.+%-01$"))
|
|
873
873
|
end)
|
|
874
874
|
|
|
875
|
-
it("records a failed span on error", function()
|
|
875
|
+
if has_feature("netsim") then it("records a failed span on error", function()
|
|
876
876
|
local h = make_client({ features = {
|
|
877
877
|
{ name = "netsim", options = { failTimes = 1, failStatus = 500 } },
|
|
878
878
|
{ name = "telemetry", options = {} },
|
|
879
879
|
} })
|
|
880
880
|
h.op({ op = "load" })
|
|
881
881
|
assert.is_false(h.client._telemetry.spans[1].ok)
|
|
882
|
-
end)
|
|
882
|
+
end) end
|
|
883
883
|
|
|
884
884
|
it("default id generation and no exporter", function()
|
|
885
885
|
local h = make_client({ features = { { name = "telemetry" } } })
|
|
@@ -925,14 +925,14 @@ describe("feature", function()
|
|
|
925
925
|
assert.are.equal("<redacted>", seen[1].headers.authorization)
|
|
926
926
|
end)
|
|
927
927
|
|
|
928
|
-
it("captures failures", function()
|
|
928
|
+
if has_feature("netsim") then it("captures failures", function()
|
|
929
929
|
local h = make_client({ features = {
|
|
930
930
|
{ name = "netsim", options = { failTimes = 1, failStatus = 500 } },
|
|
931
931
|
{ name = "debug", options = {} },
|
|
932
932
|
} })
|
|
933
933
|
h.op({ op = "load" })
|
|
934
934
|
assert.is_false(h.client._debug.entries[1].ok)
|
|
935
|
-
end)
|
|
935
|
+
end) end
|
|
936
936
|
|
|
937
937
|
it("injected clock + custom redact", function()
|
|
938
938
|
local h = make_client({ features = {
|
|
@@ -957,7 +957,7 @@ describe("feature", function()
|
|
|
957
957
|
-- audit -----------------------------------------------------------------
|
|
958
958
|
if has_feature("audit") then describe("audit", function()
|
|
959
959
|
|
|
960
|
-
it("one record per op with sink + actor", function()
|
|
960
|
+
if has_feature("netsim") then it("one record per op with sink + actor", function()
|
|
961
961
|
local sink = {}
|
|
962
962
|
local h = make_client({ features = {
|
|
963
963
|
{ name = "netsim", options = { failTimes = 1, failStatus = 500 } },
|
|
@@ -972,7 +972,7 @@ describe("feature", function()
|
|
|
972
972
|
assert.are.equal("svc", recs[1].actor)
|
|
973
973
|
assert.are.equal("per-call", recs[2].actor)
|
|
974
974
|
assert.are.equal(2, #sink)
|
|
975
|
-
end)
|
|
975
|
+
end) end
|
|
976
976
|
|
|
977
977
|
it("default actor is anonymous", function()
|
|
978
978
|
local h = make_client({ features = { { name = "audit" } } })
|
|
@@ -75,11 +75,13 @@ local function make_options_util(ctx)
|
|
|
75
75
|
|
|
76
76
|
local optspec = {
|
|
77
77
|
apikey = "",
|
|
78
|
+
secret = "",
|
|
78
79
|
base = "http://localhost:8000",
|
|
79
80
|
prefix = "",
|
|
80
81
|
suffix = "",
|
|
81
82
|
auth = {
|
|
82
83
|
prefix = "",
|
|
84
|
+
basic = false,
|
|
83
85
|
},
|
|
84
86
|
headers = {
|
|
85
87
|
["`$CHILD`"] = "`$STRING`",
|
|
@@ -813,6 +813,11 @@ let cost_feature () : feature =
|
|
|
813
813
|
end
|
|
814
814
|
end
|
|
815
815
|
| "PreDone" -> finish ctx true
|
|
816
|
+
(* A failed operation never reaches PreDone - make_error dispatches
|
|
817
|
+
PreUnexpected - so its attempts would be priced and discarded, and
|
|
818
|
+
repeated failures could slip past an onBudget "deny" ceiling.
|
|
819
|
+
`false` keeps a call that made NO attempt from being counted. *)
|
|
820
|
+
| "PreUnexpected" -> finish ctx false
|
|
816
821
|
| _ -> ());
|
|
817
822
|
f
|
|
818
823
|
|
|
@@ -937,10 +937,13 @@ let fetcher_util (ctx : ctx) (fullurl : string) (fetchdef : value) : (value * sd
|
|
|
937
937
|
let opt_spec_value () : value =
|
|
938
938
|
jo [
|
|
939
939
|
("apikey", Str "");
|
|
940
|
+
("secret", Str "");
|
|
940
941
|
("base", Str "http://localhost:8000");
|
|
941
942
|
("prefix", Str "");
|
|
942
943
|
("suffix", Str "");
|
|
943
|
-
(
|
|
944
|
+
(* `basic` and `secret`: HTTP Basic Auth needs a second credential and a
|
|
945
|
+
flag to say the pair is Basic rather than a single bearer token. *)
|
|
946
|
+
("auth", jo [("prefix", Str ""); ("basic", Bool false)]);
|
|
944
947
|
("headers", jo [("`$CHILD`", Str "`$STRING`")]);
|
|
945
948
|
("allow", jo [("method", Str "GET,PUT,POST,PATCH,DELETE,OPTIONS");
|
|
946
949
|
("op", Str "create,update,load,list,remove,command,direct,graphql")]);
|
|
@@ -29,9 +29,10 @@ unless (defined $TEST_JSON && -e $TEST_JSON) {
|
|
|
29
29
|
plan skip_all => 'test.json corpus not found';
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
# Features with a corpus section
|
|
33
|
-
#
|
|
34
|
-
|
|
32
|
+
# Features with a corpus section are read from the corpus itself (see the
|
|
33
|
+
# loop below), so a project-authored section - a custom feature added under
|
|
34
|
+
# .sdk/test/feature/ - runs without editing this file. An SDK generated
|
|
35
|
+
# without a listed feature still skips, not fails.
|
|
35
36
|
|
|
36
37
|
# The standard operation names, in the order the runner prefers them.
|
|
37
38
|
my @FEATURE_CORPUS_OPS = qw(load list create update remove);
|
|
@@ -282,7 +283,7 @@ ok(scalar(@ops) > 0,
|
|
|
282
283
|
exit 0;
|
|
283
284
|
};
|
|
284
285
|
|
|
285
|
-
for my $name (
|
|
286
|
+
for my $name (sort keys %{ $CORPUS->{feature} || {} }) {
|
|
286
287
|
my $section = ($CORPUS->{feature} || {})->{$name};
|
|
287
288
|
next unless defined $section;
|
|
288
289
|
|
|
@@ -108,9 +108,12 @@ $REGISTRY{make_options} = sub {
|
|
|
108
108
|
my $optspec = {
|
|
109
109
|
'apikey' => '',
|
|
110
110
|
'base' => 'http://localhost:8000',
|
|
111
|
+
'secret' => '',
|
|
111
112
|
'prefix' => '',
|
|
112
113
|
'suffix' => '',
|
|
113
|
-
|
|
114
|
+
# `basic` and `secret`: HTTP Basic Auth needs a second credential and a
|
|
115
|
+
# flag to say the pair is Basic rather than a single bearer token.
|
|
116
|
+
'auth' => { 'prefix' => '', 'basic' => $JF },
|
|
114
117
|
'headers' => { '`$CHILD`' => '`$STRING`' },
|
|
115
118
|
'allow' => {
|
|
116
119
|
'method' => 'GET,PUT,POST,PATCH,DELETE,OPTIONS',
|
|
@@ -69,10 +69,16 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
|
69
69
|
if (!is_string($restf)) {
|
|
70
70
|
return $data;
|
|
71
71
|
}
|
|
72
|
-
if (!preg_match('/^`body\.(
|
|
72
|
+
if (!preg_match('/^`body\.(.+)`$/', $restf, $m)) {
|
|
73
73
|
return $data;
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
// Multi-segment on purpose: GraphQL ops unwrap body.data.<field>
|
|
76
|
+
// (and body.data.<field>.<entity> for mutations), not just one level.
|
|
77
|
+
$out = $data;
|
|
78
|
+
foreach (array_reverse(explode('.', $m[1])) as $seg) {
|
|
79
|
+
$out = [$seg => $out];
|
|
80
|
+
}
|
|
81
|
+
return $out;
|
|
76
82
|
};
|
|
77
83
|
|
|
78
84
|
$respond = function (int $status, mixed $data, ?array $extra = null) use ($envelope): array {
|
|
@@ -433,13 +439,18 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
|
433
439
|
}
|
|
434
440
|
}
|
|
435
441
|
}
|
|
442
|
+
// Path AND query: a path-only read misses a query-addressed
|
|
443
|
+
// record (e.g. GET /result?trace_id=), which has no path param.
|
|
436
444
|
$params = is_array($point) ? ($point['args']['params'] ?? null) : null;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if ($
|
|
442
|
-
$
|
|
445
|
+
$query = is_array($point) ? ($point['args']['query'] ?? null) : null;
|
|
446
|
+
foreach ([$params, $query] as $arglist) {
|
|
447
|
+
if (is_array($arglist)) {
|
|
448
|
+
foreach ($arglist as $p) {
|
|
449
|
+
if (is_array($p) && (($p['reqd'] ?? false) === true)) {
|
|
450
|
+
$n = $p['name'] ?? null;
|
|
451
|
+
if ($n !== null) {
|
|
452
|
+
$reqd_names[] = $n;
|
|
453
|
+
}
|
|
443
454
|
}
|
|
444
455
|
}
|
|
445
456
|
}
|
|
@@ -23,9 +23,10 @@ use PHPUnit\Framework\TestCase;
|
|
|
23
23
|
|
|
24
24
|
class FeatureCorpusTest extends TestCase
|
|
25
25
|
{
|
|
26
|
-
// Features with a corpus section
|
|
27
|
-
//
|
|
28
|
-
|
|
26
|
+
// Features with a corpus section are read from the corpus itself (see
|
|
27
|
+
// testFeatureCorpus), so a project-authored section - a custom feature
|
|
28
|
+
// added under .sdk/test/feature/ - runs without editing this file. An
|
|
29
|
+
// SDK generated without a listed feature still skips, not fails.
|
|
29
30
|
|
|
30
31
|
// The standard operation names, in the order the runner prefers them.
|
|
31
32
|
private const FEATURE_CORPUS_OPS = ['load', 'list', 'create', 'update', 'remove'];
|
|
@@ -300,7 +301,9 @@ class FeatureCorpusTest extends TestCase
|
|
|
300
301
|
. 'the corpus (create-sdkgen .sdk/test/feature/) to run these cases');
|
|
301
302
|
}
|
|
302
303
|
|
|
303
|
-
|
|
304
|
+
$names = array_keys(self::corpus()['feature'] ?? []);
|
|
305
|
+
sort($names);
|
|
306
|
+
foreach ($names as $name) {
|
|
304
307
|
$section = self::corpus()['feature'][$name] ?? null;
|
|
305
308
|
if (null === $section) {
|
|
306
309
|
continue;
|
|
@@ -130,10 +130,11 @@ class ProjectNameMakeOptions
|
|
|
130
130
|
|
|
131
131
|
$optspec = [
|
|
132
132
|
'apikey' => '',
|
|
133
|
+
'secret' => '',
|
|
133
134
|
'base' => 'http://localhost:8000',
|
|
134
135
|
'prefix' => '',
|
|
135
136
|
'suffix' => '',
|
|
136
|
-
'auth' => ['prefix' => ''],
|
|
137
|
+
'auth' => ['prefix' => '', 'basic' => false],
|
|
137
138
|
'headers' => ['`$CHILD`' => '`$STRING`'],
|
|
138
139
|
'allow' => [
|
|
139
140
|
'method' => 'GET,PUT,POST,PATCH,DELETE,OPTIONS',
|
|
@@ -11,7 +11,7 @@ from projectname_sdk.feature.base_feature import ProjectNameBaseFeature
|
|
|
11
11
|
|
|
12
12
|
# The `body.<key>` form of an op's response transform: the mock wraps its
|
|
13
13
|
# payload in <key> so the transform can unwrap it again.
|
|
14
|
-
ENVELOPE_RES_RE = re.compile(r"^`body\.(
|
|
14
|
+
ENVELOPE_RES_RE = re.compile(r"^`body\.(.+)`$")
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class ProjectNameTestFeature(ProjectNameBaseFeature):
|
|
@@ -60,7 +60,14 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
|
|
|
60
60
|
if not isinstance(restf, str):
|
|
61
61
|
return data
|
|
62
62
|
m = ENVELOPE_RES_RE.match(restf)
|
|
63
|
-
|
|
63
|
+
if m is None:
|
|
64
|
+
return data
|
|
65
|
+
# Multi-segment on purpose: GraphQL ops unwrap body.data.<field>
|
|
66
|
+
# (and body.data.<field>.<entity> for mutations), not just one level.
|
|
67
|
+
out = data
|
|
68
|
+
for seg in reversed(m.group(1).split(".")):
|
|
69
|
+
out = {seg: out}
|
|
70
|
+
return out
|
|
64
71
|
|
|
65
72
|
def respond(status, data, extra=None):
|
|
66
73
|
payload = envelope(data)
|
|
@@ -260,10 +267,14 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
|
|
|
260
267
|
points = vs.getpath(ctx.config, "entity." + ctx.entity.get_name() + ".op." + opname + ".points")
|
|
261
268
|
point = vs.getelem(points, -1)
|
|
262
269
|
|
|
263
|
-
#
|
|
270
|
+
# Path AND query: a path-only read misses a query-addressed record
|
|
271
|
+
# (e.g. GET /result?trace_id=), which has no path param at all.
|
|
264
272
|
params_path = vs.getpath(point, "args.params")
|
|
265
273
|
reqd_params = vs.select(params_path, {"reqd": True})
|
|
266
|
-
|
|
274
|
+
query_path = vs.getpath(point, "args.query")
|
|
275
|
+
reqd_query = vs.select(query_path, {"reqd": True})
|
|
276
|
+
reqd = (vs.transform(reqd_params, ["`$EACH`", "", "`$KEY.name`"]) or []) + \
|
|
277
|
+
(vs.transform(reqd_query, ["`$EACH`", "", "`$KEY.name`"]) or [])
|
|
267
278
|
|
|
268
279
|
qand = []
|
|
269
280
|
q = {"`$AND`": qand}
|
|
@@ -97,11 +97,13 @@ def make_options_util(ctx):
|
|
|
97
97
|
|
|
98
98
|
optspec = {
|
|
99
99
|
"apikey": "",
|
|
100
|
+
"secret": "",
|
|
100
101
|
"base": "http://localhost:8000",
|
|
101
102
|
"prefix": "",
|
|
102
103
|
"suffix": "",
|
|
103
104
|
"auth": {
|
|
104
105
|
"prefix": "",
|
|
106
|
+
"basic": False,
|
|
105
107
|
},
|
|
106
108
|
"headers": {
|
|
107
109
|
"`$CHILD`": "`$STRING`",
|
|
@@ -105,6 +105,10 @@ class TestNetsimFeature:
|
|
|
105
105
|
@pytest.mark.skipif(not has_feature("retry"), reason="retry not in this SDK")
|
|
106
106
|
class TestRetryFeature:
|
|
107
107
|
|
|
108
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
109
|
+
# SDK was generated with it (the harness skips absent features).
|
|
110
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
111
|
+
reason="netsim not in this SDK")
|
|
108
112
|
def test_retries_transient_failures_then_succeeds(self):
|
|
109
113
|
clock = make_clock()
|
|
110
114
|
h = make_client([
|
|
@@ -115,6 +119,10 @@ class TestRetryFeature:
|
|
|
115
119
|
assert h.op(op="load")["ok"] is True
|
|
116
120
|
assert h.client._retry["attempts"] == 2
|
|
117
121
|
|
|
122
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
123
|
+
# SDK was generated with it (the harness skips absent features).
|
|
124
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
125
|
+
reason="netsim not in this SDK")
|
|
118
126
|
def test_gives_up_after_the_budget(self):
|
|
119
127
|
clock = make_clock()
|
|
120
128
|
h = make_client([
|
|
@@ -163,6 +171,10 @@ class TestRetryFeature:
|
|
|
163
171
|
assert h.op(op="load")["ok"] is True
|
|
164
172
|
assert state["n"] == 2
|
|
165
173
|
|
|
174
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
175
|
+
# SDK was generated with it (the harness skips absent features).
|
|
176
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
177
|
+
reason="netsim not in this SDK")
|
|
166
178
|
def test_honours_a_server_retry_after(self):
|
|
167
179
|
clock = make_clock()
|
|
168
180
|
h = make_client([
|
|
@@ -174,6 +186,10 @@ class TestRetryFeature:
|
|
|
174
186
|
assert h.op(op="load")["ok"] is True
|
|
175
187
|
assert clock.time == 2000
|
|
176
188
|
|
|
189
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
190
|
+
# SDK was generated with it (the harness skips absent features).
|
|
191
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
192
|
+
reason="netsim not in this SDK")
|
|
177
193
|
def test_default_jitter_path_still_succeeds(self):
|
|
178
194
|
h = make_client([
|
|
179
195
|
{"name": "netsim", "options": {"failTimes": 1}},
|
|
@@ -202,6 +218,10 @@ class TestRetryFeature:
|
|
|
202
218
|
@pytest.mark.skipif(not has_feature("timeout"), reason="timeout not in this SDK")
|
|
203
219
|
class TestTimeoutFeature:
|
|
204
220
|
|
|
221
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
222
|
+
# SDK was generated with it (the harness skips absent features).
|
|
223
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
224
|
+
reason="netsim not in this SDK")
|
|
205
225
|
def test_a_slow_request_times_out(self):
|
|
206
226
|
clock = make_clock()
|
|
207
227
|
h = make_client([
|
|
@@ -438,6 +458,10 @@ class TestRbacFeature:
|
|
|
438
458
|
@pytest.mark.skipif(not has_feature("metrics"), reason="metrics not in this SDK")
|
|
439
459
|
class TestMetricsFeature:
|
|
440
460
|
|
|
461
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
462
|
+
# SDK was generated with it (the harness skips absent features).
|
|
463
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
464
|
+
reason="netsim not in this SDK")
|
|
441
465
|
def test_counts_ok_and_err_per_op(self):
|
|
442
466
|
h = make_client([
|
|
443
467
|
{"name": "netsim", "options": {"failTimes": 1, "failStatus": 500}},
|
|
@@ -490,6 +514,10 @@ class TestTelemetryFeature:
|
|
|
490
514
|
assert sent["X-Trace-Id"] == h.client._telemetry["spans"][0]["traceId"]
|
|
491
515
|
assert re.match(r"^00-.+-.+-01$", sent["traceparent"])
|
|
492
516
|
|
|
517
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
518
|
+
# SDK was generated with it (the harness skips absent features).
|
|
519
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
520
|
+
reason="netsim not in this SDK")
|
|
493
521
|
def test_records_a_failed_span_on_error(self):
|
|
494
522
|
h = make_client([
|
|
495
523
|
{"name": "netsim", "options": {"failTimes": 1, "failStatus": 500}},
|
|
@@ -498,6 +526,10 @@ class TestTelemetryFeature:
|
|
|
498
526
|
h.op(op="load")
|
|
499
527
|
assert h.client._telemetry["spans"][0]["ok"] is False
|
|
500
528
|
|
|
529
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
530
|
+
# SDK was generated with it (the harness skips absent features).
|
|
531
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
532
|
+
reason="netsim not in this SDK")
|
|
501
533
|
def test_closes_each_span_exactly_once(self):
|
|
502
534
|
h = make_client([
|
|
503
535
|
{"name": "netsim", "options": {"failTimes": 1, "failStatus": 500}},
|
|
@@ -542,6 +574,10 @@ class TestDebugFeature:
|
|
|
542
574
|
assert len(seen) == 2
|
|
543
575
|
assert seen[0]["headers"]["authorization"] == "<redacted>"
|
|
544
576
|
|
|
577
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
578
|
+
# SDK was generated with it (the harness skips absent features).
|
|
579
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
580
|
+
reason="netsim not in this SDK")
|
|
545
581
|
def test_captures_failures(self):
|
|
546
582
|
h = make_client([
|
|
547
583
|
{"name": "netsim", "options": {"failTimes": 1, "failStatus": 500}},
|
|
@@ -571,6 +607,10 @@ class TestDebugFeature:
|
|
|
571
607
|
@pytest.mark.skipif(not has_feature("audit"), reason="audit not in this SDK")
|
|
572
608
|
class TestAuditFeature:
|
|
573
609
|
|
|
610
|
+
# Drives netsim as the simulated network: runnable only when this
|
|
611
|
+
# SDK was generated with it (the harness skips absent features).
|
|
612
|
+
@pytest.mark.skipif(not has_feature("netsim"),
|
|
613
|
+
reason="netsim not in this SDK")
|
|
574
614
|
def test_one_record_per_op_with_sink_and_actor(self):
|
|
575
615
|
sink = []
|
|
576
616
|
h = make_client([
|
|
@@ -23,9 +23,20 @@ from projectname_sdk import ProjectNameSDK
|
|
|
23
23
|
|
|
24
24
|
_TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
25
25
|
|
|
26
|
-
# Features with a corpus section
|
|
27
|
-
#
|
|
28
|
-
|
|
26
|
+
# Features with a corpus section — read from the corpus itself, so a
|
|
27
|
+
# project-authored section (a custom feature added under
|
|
28
|
+
# .sdk/test/feature/) runs without editing this file. Read eagerly:
|
|
29
|
+
# parametrize needs the names at collection time. An SDK generated
|
|
30
|
+
# without a listed feature still skips, not fails.
|
|
31
|
+
def _corpus_feature_names():
|
|
32
|
+
try:
|
|
33
|
+
with open(os.path.join(_TEST_DIR, "../../.sdk/test/test.json"), "r") as f:
|
|
34
|
+
return sorted((json.loads(f.read()).get("feature") or {}).keys())
|
|
35
|
+
except OSError:
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
FEATURE_CORPUS_NAMES = _corpus_feature_names()
|
|
29
40
|
|
|
30
41
|
# The standard operation names, in the order the runner prefers them.
|
|
31
42
|
FEATURE_CORPUS_OPS = ["load", "list", "create", "update", "remove"]
|
|
@@ -19,9 +19,10 @@ require_relative "../ProjectName_sdk"
|
|
|
19
19
|
|
|
20
20
|
class FeatureCorpusTest < Minitest::Test
|
|
21
21
|
|
|
22
|
-
# Features with a corpus section
|
|
23
|
-
#
|
|
24
|
-
|
|
22
|
+
# Features with a corpus section are read from the corpus itself (see
|
|
23
|
+
# test_feature_corpus), so a project-authored section - a custom feature
|
|
24
|
+
# added under .sdk/test/feature/ - runs without editing this file. An SDK
|
|
25
|
+
# generated without a listed feature still skips, not fails.
|
|
25
26
|
|
|
26
27
|
# The standard operation names, in the order the runner prefers them.
|
|
27
28
|
FEATURE_CORPUS_OPS = %w[load list create update remove].freeze
|
|
@@ -192,6 +193,13 @@ class FeatureCorpusTest < Minitest::Test
|
|
|
192
193
|
end
|
|
193
194
|
|
|
194
195
|
def record(client, name)
|
|
196
|
+
# A corpus key is any manifest item name, so it may carry characters
|
|
197
|
+
# (foo-bar, foo.2) that cannot form a Ruby instance-variable name. The
|
|
198
|
+
# probe uses this lookup to SKIP such a section, so answer nil rather
|
|
199
|
+
# than letting instance_variable_get raise NameError for a feature this
|
|
200
|
+
# SDK could never have stored under that name anyway.
|
|
201
|
+
return nil unless name.match?(/\A[A-Za-z0-9_]+\z/)
|
|
202
|
+
|
|
195
203
|
client.instance_variable_get(:"@_#{name}")
|
|
196
204
|
end
|
|
197
205
|
|
|
@@ -218,7 +226,7 @@ class FeatureCorpusTest < Minitest::Test
|
|
|
218
226
|
end
|
|
219
227
|
|
|
220
228
|
def test_feature_corpus
|
|
221
|
-
|
|
229
|
+
(corpus["feature"] || {}).keys.sort.each do |name|
|
|
222
230
|
section = (corpus["feature"] || {})[name]
|
|
223
231
|
next if section.nil?
|
|
224
232
|
|
|
@@ -69,9 +69,12 @@ module ProjectNameUtilities
|
|
|
69
69
|
optspec = {
|
|
70
70
|
"apikey" => "",
|
|
71
71
|
"base" => "http://localhost:8000",
|
|
72
|
+
"secret" => "",
|
|
72
73
|
"prefix" => "",
|
|
73
74
|
"suffix" => "",
|
|
74
|
-
|
|
75
|
+
# `basic` and `secret`: HTTP Basic Auth needs a second credential and
|
|
76
|
+
# a flag to say the pair is Basic rather than a single bearer token.
|
|
77
|
+
"auth" => { "prefix" => "", "basic" => false },
|
|
75
78
|
"headers" => { "`$CHILD`" => "`$STRING`" },
|
|
76
79
|
"allow" => {
|
|
77
80
|
"method" => "GET,PUT,POST,PATCH,DELETE,OPTIONS",
|
|
@@ -43,6 +43,13 @@ function ownIdField(config: any, getpath: any, entityName: string): string {
|
|
|
43
43
|
// key, so move on to the next op rather than guess from it.
|
|
44
44
|
const lastPart = 0 < parts.length ? String(parts[parts.length - 1]) : ''
|
|
45
45
|
if (lastPart.startsWith('{')) return lastPart.slice(1, -1)
|
|
46
|
+
|
|
47
|
+
// No path param at all (or the route ends in a literal, e.g.
|
|
48
|
+
// /orgs/{org}/private-registries/public-key): a single required QUERY
|
|
49
|
+
// param can still be the record's own key (e.g. GET /result?trace_id=).
|
|
50
|
+
const query = (best && best.args && best.args.query) || []
|
|
51
|
+
const reqdQuery = query.filter((q: any) => false !== q.reqd)
|
|
52
|
+
if (1 === reqdQuery.length) return String(reqdQuery[0].name)
|
|
46
53
|
}
|
|
47
54
|
return 'id'
|
|
48
55
|
}
|
|
@@ -332,10 +339,17 @@ class TestFeature extends BaseFeature {
|
|
|
332
339
|
}
|
|
333
340
|
}
|
|
334
341
|
|
|
335
|
-
|
|
342
|
+
// Path AND query: a path-only read misses a query-addressed record
|
|
343
|
+
// (e.g. GET /result?trace_id=), which has no path param at all.
|
|
344
|
+
const reqdParams = transform(
|
|
336
345
|
select(getpath(point, ['args', 'params']), { reqd: true }),
|
|
337
346
|
['`$EACH`', '', '`$KEY.name`']
|
|
338
347
|
)
|
|
348
|
+
const reqdQuery = transform(
|
|
349
|
+
select(getpath(point, ['args', 'query']), { reqd: true }),
|
|
350
|
+
['`$EACH`', '', '`$KEY.name`']
|
|
351
|
+
)
|
|
352
|
+
const reqd = [...(reqdParams || []), ...(reqdQuery || [])]
|
|
339
353
|
|
|
340
354
|
const qand: any[] = []
|
|
341
355
|
const q = { '`$AND`': qand }
|
|
@@ -44,11 +44,13 @@ function makeOptions(ctx: Context) {
|
|
|
44
44
|
// Standard SDK option values.
|
|
45
45
|
const optspec = {
|
|
46
46
|
apikey: '',
|
|
47
|
+
secret: '',
|
|
47
48
|
base: 'http://localhost:8000',
|
|
48
49
|
prefix: '',
|
|
49
50
|
suffix: '',
|
|
50
51
|
auth: {
|
|
51
|
-
prefix: ''
|
|
52
|
+
prefix: '',
|
|
53
|
+
basic: false
|
|
52
54
|
},
|
|
53
55
|
headers: {
|
|
54
56
|
'`$CHILD`': '`$STRING`'
|
|
@@ -5,6 +5,7 @@ import { Context, Spec } from '../types'
|
|
|
5
5
|
const HEADER_auth = 'authorization'
|
|
6
6
|
|
|
7
7
|
const OPTION_apikey = 'apikey'
|
|
8
|
+
const OPTION_secret = 'secret'
|
|
8
9
|
|
|
9
10
|
const NOTFOUND = '__NOTFOUND__'
|
|
10
11
|
|
|
@@ -40,6 +41,25 @@ function prepareAuth(ctx: Context): Spec | Error {
|
|
|
40
41
|
|
|
41
42
|
const apikey = getprop(options, OPTION_apikey, NOTFOUND)
|
|
42
43
|
|
|
44
|
+
// True HTTP Basic Auth needs TWO credentials, base64-joined - a single
|
|
45
|
+
// token in the header (the branch below) can never authenticate against
|
|
46
|
+
// an API that actually checks `Authorization: Basic base64(user:pass)`.
|
|
47
|
+
if (true === options.auth.basic) {
|
|
48
|
+
const secret = getprop(options, OPTION_secret, NOTFOUND)
|
|
49
|
+
const noApikey = NOTFOUND === apikey || null == apikey || '' === apikey
|
|
50
|
+
const noSecret = NOTFOUND === secret || null == secret || '' === secret
|
|
51
|
+
|
|
52
|
+
if (noApikey || noSecret) {
|
|
53
|
+
delprop(headers, HEADER_auth)
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const b64 = Buffer.from(apikey + ':' + secret).toString('base64')
|
|
57
|
+
setprop(headers, HEADER_auth, prefix ? prefix + ' ' + b64 : b64)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return spec
|
|
61
|
+
}
|
|
62
|
+
|
|
43
63
|
if (NOTFOUND === apikey || null == apikey || '' === apikey) {
|
|
44
64
|
delprop(headers, HEADER_auth)
|
|
45
65
|
}
|