@voxgig/sdkgen 4.24.0 → 4.24.2
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/Deploy.js +9 -6
- package/dist/cmp/Deploy.js.map +1 -1
- package/dist/helpers/semver.js +3 -1
- package/dist/helpers/semver.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/project/.sdk/tm/go/test/pipeline_test.go +163 -57
- package/project/.sdk/tm/go/test/primary_utility_test.go +53 -0
- package/project/.sdk/tm/java/test/PipelineTest.java +141 -48
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +67 -6
- package/project/.sdk/tm/js/test/pipeline.test.js +88 -22
- package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +47 -1
- package/project/.sdk/tm/lua/test/omni.lua +8 -1
- package/project/.sdk/tm/lua/test/pipeline_test.lua +107 -28
- package/project/.sdk/tm/lua/test/primary_utility_test.lua +83 -3
- package/project/.sdk/tm/perl/t/omni.pm +7 -1
- package/project/.sdk/tm/perl/t/pipeline.t +124 -27
- package/project/.sdk/tm/perl/t/primary_utility.t +73 -0
- package/project/.sdk/tm/php/test/PipelineTest.php +143 -26
- package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +80 -2
- package/project/.sdk/tm/py/test/test_pipeline.py +108 -31
- package/project/.sdk/tm/py/test/test_primary_utility.py +76 -1
- package/project/.sdk/tm/rb/test/pipeline_test.rb +100 -24
- package/project/.sdk/tm/rb/test/primary_utility_test.rb +59 -1
- package/project/.sdk/tm/ts/test/pipeline.test.ts +52 -11
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/Deploy.ts +11 -6
- package/src/helpers/semver.ts +3 -1
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# result_body utility (it has no parse guard), so that ts case is
|
|
14
14
|
# omitted.
|
|
15
15
|
|
|
16
|
+
import re
|
|
17
|
+
|
|
16
18
|
import pytest
|
|
17
19
|
|
|
18
20
|
from projectname_sdk import ProjectNameSDK
|
|
@@ -495,6 +497,10 @@ class TestFeatureOrdering:
|
|
|
495
497
|
|
|
496
498
|
class TestPrepareAuth:
|
|
497
499
|
|
|
500
|
+
# A cookie credential as prepare_auth writes it: `<scheme>=K` for the
|
|
501
|
+
# probe key, with no scheme prefix and nothing else in the bag.
|
|
502
|
+
COOKIE_PAIR = re.compile(r"^[^=;]+=K$")
|
|
503
|
+
|
|
498
504
|
class _AuthClient:
|
|
499
505
|
def __init__(self, options):
|
|
500
506
|
self._options = options
|
|
@@ -503,57 +509,128 @@ class TestPrepareAuth:
|
|
|
503
509
|
return self._options
|
|
504
510
|
|
|
505
511
|
# Fake client so the exact options.auth / apikey shape is controlled.
|
|
506
|
-
def _auth_ctx(self, client, options,
|
|
512
|
+
def _auth_ctx(self, client, options, spec):
|
|
507
513
|
utility = client._utility
|
|
508
514
|
ctx = utility.make_context({"opname": "load"}, client.get_root_ctx())
|
|
509
515
|
ctx.client = self._AuthClient(options)
|
|
510
|
-
ctx.spec =
|
|
511
|
-
{"headers": headers})
|
|
516
|
+
ctx.spec = spec
|
|
512
517
|
return ctx
|
|
513
518
|
|
|
519
|
+
@staticmethod
|
|
520
|
+
def _bags():
|
|
521
|
+
return ProjectNameSpec({"headers": {}, "query": {}})
|
|
522
|
+
|
|
523
|
+
@staticmethod
|
|
524
|
+
def _bag(spec, where):
|
|
525
|
+
# A cookie credential rides the header bag, because a cookie IS a
|
|
526
|
+
# header.
|
|
527
|
+
return spec.query if "query" == where else spec.headers
|
|
528
|
+
|
|
529
|
+
@staticmethod
|
|
530
|
+
def _auth(prefix):
|
|
531
|
+
# `basic: false` is explicit: an HTTP Basic API's generated config
|
|
532
|
+
# carries `auth.basic: true`, and a client that merges it in takes a
|
|
533
|
+
# branch that needs a secret as well. With none supplied that branch
|
|
534
|
+
# deliberately writes nothing, which the probe reads as a public API.
|
|
535
|
+
return {"prefix": prefix, "basic": False}
|
|
536
|
+
|
|
537
|
+
# Run prepare_auth with both containers present and see which one the
|
|
538
|
+
# generated utility writes to, and under what name. None means this SDK
|
|
539
|
+
# places no credential at all - a public API - which is a legitimate
|
|
540
|
+
# shape, and the tests below assert exactly that instead. `pair` is the
|
|
541
|
+
# `<scheme>=` lead-in of a COOKIE credential, which rides the header bag
|
|
542
|
+
# under the key `cookie` instead of taking a header of its own.
|
|
543
|
+
def _probe(self, client, options):
|
|
544
|
+
ctx = self._auth_ctx(client, options, self._bags())
|
|
545
|
+
client._utility.prepare_auth(ctx)
|
|
546
|
+
for where in ("headers", "query"):
|
|
547
|
+
bag = self._bag(ctx.spec, where)
|
|
548
|
+
for name in bag:
|
|
549
|
+
value = bag[name]
|
|
550
|
+
pair = ""
|
|
551
|
+
if ("headers" == where and "cookie" == name
|
|
552
|
+
and isinstance(value, str)
|
|
553
|
+
and self.COOKIE_PAIR.match(value)):
|
|
554
|
+
pair = value[:-1]
|
|
555
|
+
return {"where": where, "name": name, "value": value, "pair": pair}
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
def _credential(self, client):
|
|
559
|
+
return self._probe(
|
|
560
|
+
client, {"apikey": "K", "auth": self._auth("Bearer")})
|
|
561
|
+
|
|
562
|
+
# Every credential this SDK could possibly place: both credentials and
|
|
563
|
+
# Basic switched on, so whichever branch the API has, something lands
|
|
564
|
+
# unless the API is public.
|
|
565
|
+
def _any_credential(self, client):
|
|
566
|
+
return self._probe(client, {
|
|
567
|
+
"apikey": "K", "secret": "S",
|
|
568
|
+
"auth": {"prefix": "Bearer", "basic": True}})
|
|
569
|
+
|
|
570
|
+
def _placed(self, client, options, seed=None):
|
|
571
|
+
cred = self._credential(client)
|
|
572
|
+
spec = self._bags()
|
|
573
|
+
if cred is not None and seed is not None:
|
|
574
|
+
self._bag(spec, cred["where"])[cred["name"]] = seed
|
|
575
|
+
ctx = self._auth_ctx(client, options, spec)
|
|
576
|
+
client._utility.prepare_auth(ctx)
|
|
577
|
+
if cred is None:
|
|
578
|
+
return None
|
|
579
|
+
return self._bag(ctx.spec, cred["where"]).get(cred["name"])
|
|
580
|
+
|
|
514
581
|
def test_guards_a_missing_spec(self):
|
|
515
582
|
client = _client()
|
|
516
583
|
ctx = self._auth_ctx(client,
|
|
517
|
-
{"auth":
|
|
584
|
+
{"auth": self._auth(""), "apikey": "K"}, None)
|
|
518
585
|
_, err = client._utility.prepare_auth(ctx)
|
|
519
586
|
assert err.code == "auth_no_spec"
|
|
520
587
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
588
|
+
# Without this the cases below cannot fail for an SDK whose credential the
|
|
589
|
+
# probe misses: every one of them takes the public-API path instead.
|
|
590
|
+
def test_the_probe_finds_the_credential_this_sdk_places(self):
|
|
591
|
+
client = _client()
|
|
592
|
+
assert (self._credential(client) is None) == (
|
|
593
|
+
self._any_credential(client) is None)
|
|
594
|
+
|
|
595
|
+
def test_the_apikey_is_placed_where_this_api_puts_it(self):
|
|
596
|
+
client = _client()
|
|
597
|
+
cred = self._credential(client)
|
|
598
|
+
if cred is None:
|
|
599
|
+
# A public API places nothing, and that is the whole assertion.
|
|
600
|
+
assert self._placed(
|
|
601
|
+
client, {"apikey": "K", "auth": self._auth("Bearer")}) is None
|
|
602
|
+
return
|
|
603
|
+
assert cred["where"] in ("headers", "query")
|
|
604
|
+
if "" != cred["pair"]:
|
|
605
|
+
# A cookie credential is a `<scheme>=<key>` pair, and the scheme
|
|
606
|
+
# name leaves no room for the option's prefix.
|
|
607
|
+
assert self.COOKIE_PAIR.match(cred["value"]), cred["value"]
|
|
608
|
+
return
|
|
609
|
+
# A header credential is prefix-joined; a query credential is the raw
|
|
610
|
+
# key, because a query parameter has nowhere to put a scheme name.
|
|
611
|
+
assert cred["value"] == ("K" if "query" == cred["where"] else "Bearer K")
|
|
528
612
|
|
|
529
613
|
def test_a_raw_apikey_goes_in_as_is(self):
|
|
530
614
|
client = _client()
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
615
|
+
cred = self._credential(client)
|
|
616
|
+
expected = None if cred is None else cred["pair"] + "K"
|
|
617
|
+
assert self._placed(
|
|
618
|
+
client, {"apikey": "K", "auth": self._auth("")}) == expected
|
|
535
619
|
|
|
536
|
-
def
|
|
620
|
+
def test_an_empty_apikey_drops_the_credential(self):
|
|
537
621
|
client = _client()
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
client._utility.prepare_auth(ctx)
|
|
542
|
-
assert ctx.spec.headers.get("authorization") is None
|
|
622
|
+
assert self._placed(
|
|
623
|
+
client, {"apikey": "", "auth": self._auth("Bearer")},
|
|
624
|
+
"stale") is None
|
|
543
625
|
|
|
544
|
-
def
|
|
626
|
+
def test_a_public_api_with_no_auth_block_drops_the_credential(self):
|
|
545
627
|
client = _client()
|
|
546
|
-
|
|
547
|
-
{"authorization": "stale"})
|
|
548
|
-
client._utility.prepare_auth(ctx)
|
|
549
|
-
assert ctx.spec.headers.get("authorization") is None
|
|
628
|
+
assert self._placed(client, {"apikey": "K"}, "stale") is None
|
|
550
629
|
|
|
551
|
-
def
|
|
630
|
+
def test_a_missing_apikey_option_drops_the_credential(self):
|
|
552
631
|
client = _client()
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
client._utility.prepare_auth(ctx)
|
|
556
|
-
assert ctx.spec.headers.get("authorization") is None
|
|
632
|
+
assert self._placed(
|
|
633
|
+
client, {"auth": self._auth("Bearer")}, "stale") is None
|
|
557
634
|
|
|
558
635
|
|
|
559
636
|
class TestResultHelpers:
|
|
@@ -47,6 +47,63 @@ client = _run['client']['sdk']
|
|
|
47
47
|
utility = client._utility
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def _auth_bag(spec_obj, where):
|
|
51
|
+
# A cookie credential rides the header bag, because a cookie IS a header.
|
|
52
|
+
return spec_obj.query if "query" == where else spec_obj.headers
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _AuthProbeClient:
|
|
56
|
+
"""Minimal stand-in so the probe below controls the apikey: the suite's
|
|
57
|
+
own client carries whatever options a project configured, and an empty
|
|
58
|
+
apikey places no credential to find."""
|
|
59
|
+
|
|
60
|
+
def options_map(self):
|
|
61
|
+
# `basic: False`: a Basic API's config defaults it true, and the branch
|
|
62
|
+
# it selects needs a secret, so the probe would find nothing.
|
|
63
|
+
return {"apikey": "PROBE", "auth": {"prefix": "", "basic": False}}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _auth_credential():
|
|
67
|
+
"""Which container the generated prepare_auth writes the credential
|
|
68
|
+
into, and under what name. The name is the API's OWN scheme name, so it
|
|
69
|
+
is discovered by running prepare_auth once rather than assumed."""
|
|
70
|
+
ctx = _make_test_ctx(client, utility)
|
|
71
|
+
ctx.client = _AuthProbeClient()
|
|
72
|
+
ctx.spec = ProjectNameSpec({"headers": {}, "query": {}})
|
|
73
|
+
try:
|
|
74
|
+
utility.prepare_auth(ctx)
|
|
75
|
+
except Exception:
|
|
76
|
+
return None
|
|
77
|
+
for where in ("headers", "query"):
|
|
78
|
+
bag = _auth_bag(ctx.spec, where)
|
|
79
|
+
for name in bag:
|
|
80
|
+
return {"where": where, "name": name}
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _retarget_auth(node, cred):
|
|
85
|
+
"""Rename the corpus's `headers` bag to the real container, and the
|
|
86
|
+
`authorization` key inside it to the real credential name. Applied only
|
|
87
|
+
to the prepareAuth section, so real header assertions elsewhere are
|
|
88
|
+
untouched."""
|
|
89
|
+
if isinstance(node, list):
|
|
90
|
+
return [_retarget_auth(n, cred) for n in node]
|
|
91
|
+
if not isinstance(node, dict):
|
|
92
|
+
return node
|
|
93
|
+
out = {}
|
|
94
|
+
for key in node:
|
|
95
|
+
if "headers" == key:
|
|
96
|
+
bag = {}
|
|
97
|
+
inner = node[key] if isinstance(node[key], dict) else {}
|
|
98
|
+
for bagkey in inner:
|
|
99
|
+
name = cred["name"] if "authorization" == bagkey else bagkey
|
|
100
|
+
bag[name] = _retarget_auth(inner[bagkey], cred)
|
|
101
|
+
out[cred["where"]] = bag
|
|
102
|
+
else:
|
|
103
|
+
out[key] = _retarget_auth(node[key], cred)
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
|
|
50
107
|
# Sections deliberately left empty in the shared corpus
|
|
51
108
|
# (.sdk/test/primary/<name>.aon carries a PENDING header). Everything else
|
|
52
109
|
# MUST contribute cases.
|
|
@@ -538,7 +595,25 @@ class TestPrimaryUtility:
|
|
|
538
595
|
ctx.client = auth_client
|
|
539
596
|
return _unwrap(utility.prepare_auth(ctx))
|
|
540
597
|
|
|
541
|
-
|
|
598
|
+
# The corpus writes the credential as `headers.authorization`: a
|
|
599
|
+
# PLACEHOLDER each runner points at the container and name this API
|
|
600
|
+
# actually uses.
|
|
601
|
+
cred = _auth_credential()
|
|
602
|
+
assert cred is not None, \
|
|
603
|
+
'prepare_auth placed no credential in headers or query'
|
|
604
|
+
|
|
605
|
+
# An absent section is runsection's report to make.
|
|
606
|
+
section = spec.get('prepareAuth') if isinstance(spec, dict) else None
|
|
607
|
+
swap = isinstance(section, dict) and (
|
|
608
|
+
'headers' != cred['where'] or 'authorization' != cred['name'])
|
|
609
|
+
original = section['basic'] if swap else None
|
|
610
|
+
if swap:
|
|
611
|
+
section['basic'] = _retarget_auth(original, cred)
|
|
612
|
+
try:
|
|
613
|
+
runsection('prepareAuth', subject)
|
|
614
|
+
finally:
|
|
615
|
+
if swap:
|
|
616
|
+
section['basic'] = original
|
|
542
617
|
|
|
543
618
|
def test_prepare_body_basic(self):
|
|
544
619
|
runsection('prepareBody', lambda ctx: utility.prepare_body(ctx))
|
|
@@ -513,52 +513,128 @@ class PipelineTest < Minitest::Test
|
|
|
513
513
|
|
|
514
514
|
# === prepare_auth ===
|
|
515
515
|
|
|
516
|
-
|
|
516
|
+
# A cookie credential as prepare_auth writes it: `<scheme>=K` for the probe
|
|
517
|
+
# key, with no scheme prefix and nothing else in the bag.
|
|
518
|
+
COOKIE_PAIR = /\A[^=;]+=K\z/
|
|
519
|
+
|
|
520
|
+
def auth_ctx(options, spec)
|
|
517
521
|
ctx = ProjectNameContext.new({
|
|
518
522
|
"client" => OptClient.new(options),
|
|
519
523
|
"utility" => @utility,
|
|
520
524
|
"opname" => "load",
|
|
521
525
|
}, nil)
|
|
522
|
-
ctx.spec =
|
|
526
|
+
ctx.spec = spec
|
|
523
527
|
ctx
|
|
524
528
|
end
|
|
525
529
|
|
|
530
|
+
def auth_bags
|
|
531
|
+
ProjectNameSpec.new({ "headers" => {}, "query" => {}, "step" => "s" })
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
# A cookie credential rides the header bag, because a cookie IS a header.
|
|
535
|
+
def auth_bag(spec, where)
|
|
536
|
+
"query" == where ? spec.query : spec.headers
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# `basic: false` is explicit: an HTTP Basic API's generated config carries
|
|
540
|
+
# `auth.basic: true`, and a client that merges it in takes a branch that
|
|
541
|
+
# needs a secret as well. With none supplied that branch deliberately writes
|
|
542
|
+
# nothing, which the probe would read as a public API.
|
|
543
|
+
def auth_block(prefix)
|
|
544
|
+
{ "prefix" => prefix, "basic" => false }
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Run prepare_auth with both containers present and see which one the
|
|
548
|
+
# generated utility writes to, and under what name. nil means this SDK
|
|
549
|
+
# places no credential at all - a public API - which is a legitimate shape,
|
|
550
|
+
# and the tests below assert exactly that instead. `pair` is the `<scheme>=`
|
|
551
|
+
# lead-in of a COOKIE credential, which rides the header bag under the key
|
|
552
|
+
# `cookie` instead of taking a header of its own.
|
|
553
|
+
def auth_probe(options)
|
|
554
|
+
ctx = auth_ctx(options, auth_bags)
|
|
555
|
+
@utility.prepare_auth.call(ctx)
|
|
556
|
+
["headers", "query"].each do |where|
|
|
557
|
+
bag = auth_bag(ctx.spec, where)
|
|
558
|
+
bag.each do |name, value|
|
|
559
|
+
pair = ""
|
|
560
|
+
if "headers" == where && "cookie" == name && value.is_a?(String) &&
|
|
561
|
+
COOKIE_PAIR.match?(value)
|
|
562
|
+
pair = value[0..-2]
|
|
563
|
+
end
|
|
564
|
+
return { "where" => where, "name" => name, "value" => value, "pair" => pair }
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
nil
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def auth_credential
|
|
571
|
+
auth_probe({ "apikey" => "K", "auth" => auth_block("Bearer") })
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Every credential this SDK could possibly place: both credentials and Basic
|
|
575
|
+
# switched on, so whichever branch the API has, something lands unless the
|
|
576
|
+
# API is public.
|
|
577
|
+
def auth_any_credential
|
|
578
|
+
auth_probe({ "apikey" => "K", "secret" => "S",
|
|
579
|
+
"auth" => { "prefix" => "Bearer", "basic" => true } })
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def auth_placed(options, seed = nil)
|
|
583
|
+
cred = auth_credential
|
|
584
|
+
spec = auth_bags
|
|
585
|
+
auth_bag(spec, cred["where"])[cred["name"]] = seed if !cred.nil? && !seed.nil?
|
|
586
|
+
ctx = auth_ctx(options, spec)
|
|
587
|
+
@utility.prepare_auth.call(ctx)
|
|
588
|
+
cred.nil? ? nil : auth_bag(ctx.spec, cred["where"])[cred["name"]]
|
|
589
|
+
end
|
|
590
|
+
|
|
526
591
|
def test_prepare_auth_guards_a_missing_spec
|
|
527
|
-
ctx = auth_ctx({ "auth" =>
|
|
592
|
+
ctx = auth_ctx({ "auth" => auth_block(""), "apikey" => "K" }, nil)
|
|
528
593
|
_, err = @utility.prepare_auth.call(ctx)
|
|
529
594
|
assert_equal "auth_no_spec", err.code
|
|
530
595
|
end
|
|
531
596
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
597
|
+
# Without this the cases below cannot fail for an SDK whose credential the
|
|
598
|
+
# probe misses: every one of them takes the public-API path instead.
|
|
599
|
+
def test_prepare_auth_probe_finds_the_credential_this_sdk_places
|
|
600
|
+
assert_equal auth_credential.nil?, auth_any_credential.nil?
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def test_prepare_auth_places_the_apikey_where_this_api_puts_it
|
|
604
|
+
cred = auth_credential
|
|
605
|
+
if cred.nil?
|
|
606
|
+
# A public API places nothing, and that is the whole assertion.
|
|
607
|
+
assert_nil auth_placed({ "apikey" => "K", "auth" => auth_block("Bearer") })
|
|
608
|
+
return
|
|
609
|
+
end
|
|
610
|
+
assert_includes ["headers", "query"], cred["where"]
|
|
611
|
+
if "" != cred["pair"]
|
|
612
|
+
# A cookie credential is a `<scheme>=<key>` pair, and the scheme name
|
|
613
|
+
# leaves no room for the option's prefix.
|
|
614
|
+
assert_match COOKIE_PAIR, cred["value"]
|
|
615
|
+
return
|
|
616
|
+
end
|
|
617
|
+
# A header credential is prefix-joined; a query credential is the raw
|
|
618
|
+
# key, because a query parameter has nowhere to put a scheme name.
|
|
619
|
+
assert_equal("query" == cred["where"] ? "K" : "Bearer K", cred["value"])
|
|
537
620
|
end
|
|
538
621
|
|
|
539
622
|
def test_prepare_auth_a_raw_apikey_goes_in_as_is
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
assert_equal "K",
|
|
623
|
+
cred = auth_credential
|
|
624
|
+
expected = cred.nil? ? nil : cred["pair"] + "K"
|
|
625
|
+
assert_equal expected, auth_placed({ "apikey" => "K", "auth" => auth_block("") })
|
|
543
626
|
end
|
|
544
627
|
|
|
545
|
-
def
|
|
546
|
-
|
|
547
|
-
{ "authorization" => "stale" })
|
|
548
|
-
@utility.prepare_auth.call(ctx)
|
|
549
|
-
assert_nil ctx.spec.headers["authorization"]
|
|
628
|
+
def test_prepare_auth_an_empty_apikey_drops_the_credential
|
|
629
|
+
assert_nil auth_placed({ "apikey" => "", "auth" => auth_block("Bearer") }, "stale")
|
|
550
630
|
end
|
|
551
631
|
|
|
552
|
-
def
|
|
553
|
-
|
|
554
|
-
@utility.prepare_auth.call(ctx)
|
|
555
|
-
assert_nil ctx.spec.headers["authorization"]
|
|
632
|
+
def test_prepare_auth_a_public_api_drops_the_credential
|
|
633
|
+
assert_nil auth_placed({ "apikey" => "K" }, "stale")
|
|
556
634
|
end
|
|
557
635
|
|
|
558
|
-
def
|
|
559
|
-
|
|
560
|
-
@utility.prepare_auth.call(ctx)
|
|
561
|
-
assert_nil ctx.spec.headers["authorization"]
|
|
636
|
+
def test_prepare_auth_a_missing_apikey_option_drops_the_credential
|
|
637
|
+
assert_nil auth_placed({ "auth" => auth_block("Bearer") }, "stale")
|
|
562
638
|
end
|
|
563
639
|
|
|
564
640
|
|
|
@@ -533,7 +533,24 @@ class PrimaryUtilityTest < Minitest::Test
|
|
|
533
533
|
unwrap(@utility.prepare_auth.call(ctx))
|
|
534
534
|
end
|
|
535
535
|
|
|
536
|
-
|
|
536
|
+
# The corpus writes the credential as `headers.authorization`: a
|
|
537
|
+
# PLACEHOLDER each runner points at the container and name this API
|
|
538
|
+
# actually uses.
|
|
539
|
+
cred = auth_credential(auth_client)
|
|
540
|
+
refute_nil cred, "prepare_auth placed no credential in headers or query"
|
|
541
|
+
|
|
542
|
+
# An absent section is runsection's report to make.
|
|
543
|
+
section = @spec.is_a?(Hash) ? @spec["prepareAuth"] : nil
|
|
544
|
+
swap = section.is_a?(Hash) &&
|
|
545
|
+
!("headers" == cred["where"] && "authorization" == cred["name"])
|
|
546
|
+
original = swap ? section["basic"] : nil
|
|
547
|
+
section["basic"] = retarget_auth(original, cred) if swap
|
|
548
|
+
|
|
549
|
+
begin
|
|
550
|
+
runsection("prepareAuth", subject)
|
|
551
|
+
ensure
|
|
552
|
+
section["basic"] = original if swap
|
|
553
|
+
end
|
|
537
554
|
end
|
|
538
555
|
|
|
539
556
|
|
|
@@ -650,6 +667,47 @@ class PrimaryUtilityTest < Minitest::Test
|
|
|
650
667
|
utility.make_context.call(ctxmap, client.get_root_ctx)
|
|
651
668
|
end
|
|
652
669
|
|
|
670
|
+
# Where this SDK's prepare_auth actually puts the credential: the name is
|
|
671
|
+
# the API's OWN scheme name, not always `authorization`, so it is
|
|
672
|
+
# discovered by running prepare_auth once.
|
|
673
|
+
def auth_credential(client)
|
|
674
|
+
utility = client.get_utility
|
|
675
|
+
ctx = make_test_ctx(client, utility, nil)
|
|
676
|
+
ctx.spec = ProjectNameSpec.new({ "headers" => {}, "query" => {}, "step" => "s" })
|
|
677
|
+
_, err = utility.prepare_auth.call(ctx)
|
|
678
|
+
return nil unless err.nil?
|
|
679
|
+
["headers", "query"].each do |where|
|
|
680
|
+
# A cookie credential rides the header bag, because a cookie IS a header.
|
|
681
|
+
bag = "query" == where ? ctx.spec.query : ctx.spec.headers
|
|
682
|
+
bag.each { |name, _value| return { "where" => where, "name" => name } }
|
|
683
|
+
end
|
|
684
|
+
nil
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
# Rename the corpus's `headers` bag to the real container, and the
|
|
688
|
+
# `authorization` key inside it to the real credential name. Applied only
|
|
689
|
+
# to the prepareAuth section, so real header assertions elsewhere are
|
|
690
|
+
# untouched.
|
|
691
|
+
def retarget_auth(node, cred)
|
|
692
|
+
return node.map { |child| retarget_auth(child, cred) } if node.is_a?(Array)
|
|
693
|
+
return node unless node.is_a?(Hash)
|
|
694
|
+
|
|
695
|
+
out = {}
|
|
696
|
+
node.each do |key, value|
|
|
697
|
+
if "headers" == key
|
|
698
|
+
bag = {}
|
|
699
|
+
(value.is_a?(Hash) ? value : {}).each do |bagkey, bagvalue|
|
|
700
|
+
name = "authorization" == bagkey ? cred["name"] : bagkey
|
|
701
|
+
bag[name] = retarget_auth(bagvalue, cred)
|
|
702
|
+
end
|
|
703
|
+
out[cred["where"]] = bag
|
|
704
|
+
else
|
|
705
|
+
out[key] = retarget_auth(value, cred)
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
out
|
|
709
|
+
end
|
|
710
|
+
|
|
653
711
|
# === Helper: make_test_full_ctx ===
|
|
654
712
|
def make_test_full_ctx(client, utility)
|
|
655
713
|
ctx = make_test_ctx(client, utility, nil)
|
|
@@ -317,6 +317,11 @@ describe('pipeline:feature order', () => {
|
|
|
317
317
|
})
|
|
318
318
|
|
|
319
319
|
|
|
320
|
+
// A cookie credential as prepareAuth writes it: `<scheme>=K` for the probe
|
|
321
|
+
// key, with no scheme prefix and nothing else in the bag.
|
|
322
|
+
const COOKIE_PAIR = /^[^=;]+=K$/
|
|
323
|
+
|
|
324
|
+
|
|
320
325
|
describe('pipeline:prepareAuth', () => {
|
|
321
326
|
|
|
322
327
|
function authCtx(options: any, spec: any) {
|
|
@@ -327,19 +332,42 @@ describe('pipeline:prepareAuth', () => {
|
|
|
327
332
|
return { headers: {} as any, query: {} as any }
|
|
328
333
|
}
|
|
329
334
|
|
|
335
|
+
// `basic: false` is explicit: an HTTP Basic API's generated config carries
|
|
336
|
+
// `auth.basic: true`, and a client that merges it in takes a branch needing
|
|
337
|
+
// a secret as well. With none supplied that branch writes nothing, which
|
|
338
|
+
// the probe below would then read as a public API.
|
|
339
|
+
function auth(prefix: string): any {
|
|
340
|
+
return { prefix, basic: false }
|
|
341
|
+
}
|
|
342
|
+
|
|
330
343
|
// Run prepareAuth with both containers present and see which one the
|
|
331
344
|
// generated utility writes to, and under what name. Null means this SDK
|
|
332
|
-
// places no credential at all — a public API — which
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
345
|
+
// places no credential at all — a public API — which the cases below then
|
|
346
|
+
// assert instead.
|
|
347
|
+
|
|
348
|
+
// `pair` is the `<scheme>=` lead-in of a COOKIE credential, which rides the
|
|
349
|
+
// header bag under the key `cookie` rather than taking a header of its own.
|
|
350
|
+
function probe(options: any) {
|
|
351
|
+
const ctx = authCtx(options, bags())
|
|
336
352
|
;(stdutil as any).prepareAuth(ctx)
|
|
337
353
|
for (const where of ['headers', 'query'] as const) {
|
|
338
354
|
const name = Object.keys(ctx.spec[where] as any)[0]
|
|
339
|
-
if (null
|
|
355
|
+
if (null == name) continue
|
|
356
|
+
const value = ctx.spec[where][name]
|
|
357
|
+
const pair = COOKIE_PAIR.test(String(value)) && 'cookie' === name && 'headers' === where
|
|
358
|
+
? String(value).slice(0, -1)
|
|
359
|
+
: ''
|
|
360
|
+
return { where, name, value, pair }
|
|
340
361
|
}
|
|
341
362
|
return null
|
|
342
|
-
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const CRED = probe({ apikey: 'K', auth: auth('Bearer') })
|
|
366
|
+
|
|
367
|
+
// Every credential this SDK could possibly place: both credentials and
|
|
368
|
+
// Basic switched on, so whichever branch the API has, something lands
|
|
369
|
+
// unless the API is public.
|
|
370
|
+
const ANY = probe({ apikey: 'K', secret: 'S', auth: { prefix: 'Bearer', basic: true } })
|
|
343
371
|
|
|
344
372
|
function placed(options: any, seed?: any) {
|
|
345
373
|
const spec: any = bags()
|
|
@@ -350,27 +378,40 @@ describe('pipeline:prepareAuth', () => {
|
|
|
350
378
|
}
|
|
351
379
|
|
|
352
380
|
test('guards a missing spec', () => {
|
|
353
|
-
strictEqual((stdutil as any).prepareAuth(authCtx({ auth:
|
|
381
|
+
strictEqual((stdutil as any).prepareAuth(authCtx({ auth: auth(''), apikey: 'K' }, null)).code, 'auth_no_spec')
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
// Without this the cases below cannot fail for an SDK whose credential the
|
|
385
|
+
// probe misses: every one of them takes the public-API path instead.
|
|
386
|
+
test('the probe finds the credential this SDK places', () => {
|
|
387
|
+
strictEqual(null != CRED, null != ANY)
|
|
354
388
|
})
|
|
355
389
|
|
|
356
390
|
test('the apikey is placed where this API puts it', () => {
|
|
357
391
|
if (null == CRED) {
|
|
358
392
|
// A public API places nothing, and that is the whole assertion.
|
|
359
|
-
strictEqual(placed({ apikey: 'K', auth:
|
|
393
|
+
strictEqual(placed({ apikey: 'K', auth: auth('Bearer') }), undefined)
|
|
360
394
|
return
|
|
361
395
|
}
|
|
362
396
|
ok('headers' === CRED.where || 'query' === CRED.where)
|
|
397
|
+
if ('' !== CRED.pair) {
|
|
398
|
+
// A cookie credential is a `<scheme>=<key>` pair, and the scheme name
|
|
399
|
+
// leaves no room for the option's prefix.
|
|
400
|
+
ok(COOKIE_PAIR.test(String(CRED.value)), String(CRED.value))
|
|
401
|
+
return
|
|
402
|
+
}
|
|
363
403
|
// A header credential is prefix-joined; a query credential is the raw
|
|
364
404
|
// key, because a query parameter has nowhere to put a scheme name.
|
|
365
405
|
strictEqual(CRED.value, 'headers' === CRED.where ? 'Bearer K' : 'K')
|
|
366
406
|
})
|
|
367
407
|
|
|
368
408
|
test('a raw apikey (empty prefix) goes in as-is', () => {
|
|
369
|
-
|
|
409
|
+
const raw = null == CRED ? undefined : CRED.pair + 'K'
|
|
410
|
+
strictEqual(placed({ apikey: 'K', auth: auth('') }), raw)
|
|
370
411
|
})
|
|
371
412
|
|
|
372
413
|
test('an empty apikey drops the credential', () => {
|
|
373
|
-
strictEqual(placed({ apikey: '', auth:
|
|
414
|
+
strictEqual(placed({ apikey: '', auth: auth('Bearer') }, 'stale'), undefined)
|
|
374
415
|
})
|
|
375
416
|
|
|
376
417
|
test('a public API (no auth block) drops the credential', () => {
|
|
@@ -378,7 +419,7 @@ describe('pipeline:prepareAuth', () => {
|
|
|
378
419
|
})
|
|
379
420
|
|
|
380
421
|
test('a missing apikey option drops the credential', () => {
|
|
381
|
-
strictEqual(placed({ auth:
|
|
422
|
+
strictEqual(placed({ auth: auth('Bearer') }, 'stale'), undefined)
|
|
382
423
|
})
|
|
383
424
|
})
|
|
384
425
|
|