@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
|
@@ -551,60 +551,157 @@ sub named_feature {
|
|
|
551
551
|
|
|
552
552
|
# === prepare_auth ===
|
|
553
553
|
|
|
554
|
+
# A cookie credential as prepare_auth writes it: `<scheme>=K` for the probe
|
|
555
|
+
# key, with no scheme prefix and nothing else in the bag.
|
|
556
|
+
sub _is_cookie_pair {
|
|
557
|
+
my ($value) = @_;
|
|
558
|
+
return (defined $value && !ref $value && $value =~ m{^[^=;]+=K$}) ? 1 : 0;
|
|
559
|
+
}
|
|
560
|
+
|
|
554
561
|
sub auth_ctx {
|
|
555
|
-
my ($options, $
|
|
562
|
+
my ($options, $spec) = @_;
|
|
556
563
|
my $ctx = ProjectNameContext->new({
|
|
557
564
|
'client' => PipelineOptClient->new($options),
|
|
558
565
|
'utility' => $utility,
|
|
559
566
|
'opname' => 'load',
|
|
560
567
|
}, undef);
|
|
561
|
-
$ctx->{spec} =
|
|
562
|
-
? ProjectNameSpec->new({ 'headers' => $headers, 'step' => 's' })
|
|
563
|
-
: undef;
|
|
568
|
+
$ctx->{spec} = $spec;
|
|
564
569
|
return $ctx;
|
|
565
570
|
}
|
|
566
571
|
|
|
572
|
+
sub auth_bags {
|
|
573
|
+
return ProjectNameSpec->new({ 'headers' => {}, 'query' => {}, 'step' => 's' });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
# A cookie credential rides the header bag, because a cookie IS a header.
|
|
577
|
+
sub auth_bag {
|
|
578
|
+
my ($spec, $where) = @_;
|
|
579
|
+
return 'query' eq $where ? $spec->{query} : $spec->{headers};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
# `basic: false` is explicit: an HTTP Basic API's generated config carries
|
|
583
|
+
# `auth.basic: true`, and a client that merges it in takes a branch that needs
|
|
584
|
+
# a secret as well. With none supplied that branch deliberately writes
|
|
585
|
+
# nothing, which the probe would read as a public API.
|
|
586
|
+
sub auth_block {
|
|
587
|
+
my ($prefix) = @_;
|
|
588
|
+
return { 'prefix' => $prefix, 'basic' => 0 };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
# Run prepare_auth with both containers present and see which one the
|
|
592
|
+
# generated utility writes to, and under what name. undef means this SDK
|
|
593
|
+
# places no credential at all - a public API - which is a legitimate shape,
|
|
594
|
+
# and the tests below assert exactly that instead. `pair` is the `<scheme>=`
|
|
595
|
+
# lead-in of a COOKIE credential, which rides the header bag under the key
|
|
596
|
+
# `cookie` instead of taking a header of its own.
|
|
597
|
+
sub auth_probe {
|
|
598
|
+
my ($options) = @_;
|
|
599
|
+
my $ctx = auth_ctx($options, auth_bags());
|
|
600
|
+
$utility->{prepare_auth}->($ctx);
|
|
601
|
+
for my $where ('headers', 'query') {
|
|
602
|
+
my $bag = auth_bag($ctx->{spec}, $where);
|
|
603
|
+
for my $name (keys %{ $bag || {} }) {
|
|
604
|
+
my $value = $bag->{$name};
|
|
605
|
+
my $pair = '';
|
|
606
|
+
if ('headers' eq $where && 'cookie' eq $name && _is_cookie_pair($value)) {
|
|
607
|
+
$pair = substr($value, 0, -1);
|
|
608
|
+
}
|
|
609
|
+
return { 'where' => $where, 'name' => $name,
|
|
610
|
+
'value' => $value, 'pair' => $pair };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return undef;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
sub auth_credential {
|
|
617
|
+
return auth_probe({ 'apikey' => 'K', 'auth' => auth_block('Bearer') });
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
# Every credential this SDK could possibly place: both credentials and Basic
|
|
621
|
+
# switched on, so whichever branch the API has, something lands unless the API
|
|
622
|
+
# is public.
|
|
623
|
+
sub auth_any_credential {
|
|
624
|
+
return auth_probe({ 'apikey' => 'K', 'secret' => 'S',
|
|
625
|
+
'auth' => { 'prefix' => 'Bearer', 'basic' => 1 } });
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
# Returns the value left in the credential slot, and whether it is there.
|
|
629
|
+
sub auth_placed {
|
|
630
|
+
my ($options, $seed) = @_;
|
|
631
|
+
my $cred = auth_credential();
|
|
632
|
+
my $spec = auth_bags();
|
|
633
|
+
if (defined $cred && defined $seed) {
|
|
634
|
+
auth_bag($spec, $cred->{where})->{ $cred->{name} } = $seed;
|
|
635
|
+
}
|
|
636
|
+
my $ctx = auth_ctx($options, $spec);
|
|
637
|
+
$utility->{prepare_auth}->($ctx);
|
|
638
|
+
return (undef, 0) if !defined $cred;
|
|
639
|
+
my $bag = auth_bag($ctx->{spec}, $cred->{where});
|
|
640
|
+
return ($bag->{ $cred->{name} }, exists $bag->{ $cred->{name} } ? 1 : 0);
|
|
641
|
+
}
|
|
642
|
+
|
|
567
643
|
{
|
|
568
|
-
my $ctx = auth_ctx({ 'auth' =>
|
|
644
|
+
my $ctx = auth_ctx({ 'auth' => auth_block(''), 'apikey' => 'K' }, undef);
|
|
569
645
|
my (undef, $err) = $utility->{prepare_auth}->($ctx);
|
|
570
646
|
is($err->{code}, 'auth_no_spec', 'prepare_auth guards a missing spec');
|
|
571
647
|
}
|
|
572
648
|
|
|
649
|
+
# Without this the cases below cannot fail for an SDK whose credential the
|
|
650
|
+
# probe misses: every one of them takes the public-API path instead.
|
|
573
651
|
{
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
ok(!defined $err, 'prepare_auth prefix: no error');
|
|
577
|
-
is($ctx->{spec}{headers}{authorization}, 'Bearer K',
|
|
578
|
-
'prepare_auth an apikey with a prefix is space-joined');
|
|
652
|
+
is(defined auth_credential() ? 1 : 0, defined auth_any_credential() ? 1 : 0,
|
|
653
|
+
'prepare_auth probe finds the credential this SDK places');
|
|
579
654
|
}
|
|
580
655
|
|
|
581
656
|
{
|
|
582
|
-
my $
|
|
583
|
-
|
|
584
|
-
|
|
657
|
+
my $cred = auth_credential();
|
|
658
|
+
if (!defined $cred) {
|
|
659
|
+
# A public API places nothing, and that is the whole assertion.
|
|
660
|
+
my (undef, $has) =
|
|
661
|
+
auth_placed({ 'apikey' => 'K', 'auth' => auth_block('Bearer') });
|
|
662
|
+
ok(!$has, 'prepare_auth places no credential for a public API');
|
|
663
|
+
}
|
|
664
|
+
elsif ('' ne $cred->{pair}) {
|
|
665
|
+
# A cookie credential is a `<scheme>=<key>` pair, and the scheme name
|
|
666
|
+
# leaves no room for the option's prefix.
|
|
667
|
+
ok(_is_cookie_pair($cred->{value}),
|
|
668
|
+
'prepare_auth places the apikey as a cookie pair');
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
ok('headers' eq $cred->{where} || 'query' eq $cred->{where},
|
|
672
|
+
'prepare_auth places the credential in headers or query');
|
|
673
|
+
# A header credential is prefix-joined; a query credential is the raw
|
|
674
|
+
# key, because a query parameter has nowhere to put a scheme name.
|
|
675
|
+
is($cred->{value}, 'query' eq $cred->{where} ? 'K' : 'Bearer K',
|
|
676
|
+
'prepare_auth places the apikey where this API puts it');
|
|
677
|
+
}
|
|
585
678
|
}
|
|
586
679
|
|
|
587
680
|
{
|
|
588
|
-
my $
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
681
|
+
my ($value, $has) = auth_placed({ 'apikey' => 'K', 'auth' => auth_block('') });
|
|
682
|
+
my $cred = auth_credential();
|
|
683
|
+
if (defined $cred) {
|
|
684
|
+
is($value, $cred->{pair} . 'K', 'prepare_auth a raw apikey goes in as-is');
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
ok(!$has, 'prepare_auth a raw apikey places nothing for a public API');
|
|
688
|
+
}
|
|
593
689
|
}
|
|
594
690
|
|
|
595
691
|
{
|
|
596
|
-
my
|
|
597
|
-
|
|
598
|
-
ok(
|
|
599
|
-
'prepare_auth a public api drops the header');
|
|
692
|
+
my (undef, $has) =
|
|
693
|
+
auth_placed({ 'apikey' => '', 'auth' => auth_block('Bearer') }, 'stale');
|
|
694
|
+
ok(!$has, 'prepare_auth an empty apikey drops the credential');
|
|
600
695
|
}
|
|
601
696
|
|
|
602
697
|
{
|
|
603
|
-
my $
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
698
|
+
my (undef, $has) = auth_placed({ 'apikey' => 'K' }, 'stale');
|
|
699
|
+
ok(!$has, 'prepare_auth a public api drops the credential');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
{
|
|
703
|
+
my (undef, $has) = auth_placed({ 'auth' => auth_block('Bearer') }, 'stale');
|
|
704
|
+
ok(!$has, 'prepare_auth a missing apikey option drops the credential');
|
|
608
705
|
}
|
|
609
706
|
|
|
610
707
|
|
|
@@ -200,6 +200,64 @@ sub _def_setup {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
# Where this SDK's prepare_auth actually puts the credential: the name is the
|
|
204
|
+
# API's OWN scheme name, not always `authorization`, so it is discovered by
|
|
205
|
+
# running prepare_auth once.
|
|
206
|
+
sub _auth_credential {
|
|
207
|
+
my ($auth_client) = @_;
|
|
208
|
+
my $auth_utility = $auth_client->get_utility;
|
|
209
|
+
my $ctx = ProjectNameContext->new({
|
|
210
|
+
'client' => $auth_client,
|
|
211
|
+
'utility' => $auth_utility,
|
|
212
|
+
'opname' => 'load',
|
|
213
|
+
}, undef);
|
|
214
|
+
# The DEF setup this client is built from carries `auth.basic: false`, so
|
|
215
|
+
# the single-token branch runs and there is a credential to find.
|
|
216
|
+
$ctx->{spec} = ProjectNameSpec->new({
|
|
217
|
+
'headers' => {}, 'query' => {}, 'step' => 's' });
|
|
218
|
+
my (undef, $err) = $auth_utility->{prepare_auth}->($ctx);
|
|
219
|
+
return undef if defined $err;
|
|
220
|
+
for my $where ('headers', 'query') {
|
|
221
|
+
# A cookie credential rides the header bag, because a cookie IS a header.
|
|
222
|
+
my $bag = 'query' eq $where ? $ctx->{spec}{query} : $ctx->{spec}{headers};
|
|
223
|
+
for my $name (keys %{ $bag || {} }) {
|
|
224
|
+
return { 'where' => $where, 'name' => $name };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return undef;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# Rename the corpus's `headers` bag to the real container, and the
|
|
232
|
+
# `authorization` key inside it to the real credential name. Applied only to
|
|
233
|
+
# the prepareAuth section, so real header assertions elsewhere are untouched.
|
|
234
|
+
sub _retarget_auth {
|
|
235
|
+
my ($node, $cred) = @_;
|
|
236
|
+
|
|
237
|
+
if (ref($node) eq 'ARRAY') {
|
|
238
|
+
return [ map { _retarget_auth($_, $cred) } @$node ];
|
|
239
|
+
}
|
|
240
|
+
return $node unless ref($node) eq 'HASH';
|
|
241
|
+
|
|
242
|
+
my $out = {};
|
|
243
|
+
for my $key (keys %$node) {
|
|
244
|
+
if ('headers' eq $key) {
|
|
245
|
+
my $bag = {};
|
|
246
|
+
my $inner = ref($node->{$key}) eq 'HASH' ? $node->{$key} : {};
|
|
247
|
+
for my $bagkey (keys %$inner) {
|
|
248
|
+
my $name = 'authorization' eq $bagkey ? $cred->{name} : $bagkey;
|
|
249
|
+
$bag->{$name} = _retarget_auth($inner->{$bagkey}, $cred);
|
|
250
|
+
}
|
|
251
|
+
$out->{ $cred->{where} } = $bag;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
$out->{$key} = _retarget_auth($node->{$key}, $cred);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return $out;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
203
261
|
# === exists ===
|
|
204
262
|
|
|
205
263
|
for my $name (qw(
|
|
@@ -606,12 +664,27 @@ runsection('param', sub {
|
|
|
606
664
|
{
|
|
607
665
|
my $auth_client = ProjectNameSDK->test(undef, _def_setup('prepareAuth'));
|
|
608
666
|
|
|
667
|
+
# The corpus writes the credential as `headers.authorization`: a
|
|
668
|
+
# PLACEHOLDER each runner points at the container and name this API
|
|
669
|
+
# actually uses.
|
|
670
|
+
my $cred = _auth_credential($auth_client);
|
|
671
|
+
ok(defined $cred, 'prepare_auth placed a credential in headers or query');
|
|
672
|
+
|
|
673
|
+
# An absent section is runsection's report to make.
|
|
674
|
+
my $section = ref($spec) eq 'HASH' ? $spec->{prepareAuth} : undef;
|
|
675
|
+
my $swap = ref($section) eq 'HASH' && defined $cred
|
|
676
|
+
&& !('headers' eq $cred->{where} && 'authorization' eq $cred->{name});
|
|
677
|
+
my $original = $swap ? $section->{basic} : undef;
|
|
678
|
+
$section->{basic} = _retarget_auth($original, $cred) if $swap;
|
|
679
|
+
|
|
609
680
|
runsection('prepareAuth', sub {
|
|
610
681
|
my ($view) = @_;
|
|
611
682
|
my $ctx = ProjectNameOmni::livectx($view);
|
|
612
683
|
$ctx->{client} = $auth_client;
|
|
613
684
|
return _unwrap($utility->{prepare_auth}->($ctx));
|
|
614
685
|
});
|
|
686
|
+
|
|
687
|
+
$section->{basic} = $original if $swap;
|
|
615
688
|
}
|
|
616
689
|
|
|
617
690
|
|
|
@@ -611,59 +611,176 @@ class PipelineTest extends TestCase
|
|
|
611
611
|
|
|
612
612
|
// --- prepare_auth ------------------------------------------------------------
|
|
613
613
|
|
|
614
|
-
|
|
614
|
+
/**
|
|
615
|
+
* A cookie credential as prepare_auth writes it: `<scheme>=K` for the
|
|
616
|
+
* probe key, with no scheme prefix and nothing else in the bag.
|
|
617
|
+
*/
|
|
618
|
+
private const COOKIE_PAIR = '/^[^=;]+=K$/';
|
|
619
|
+
|
|
620
|
+
private static function auth_ctx(array $options, ?ProjectNameSpec $spec): ProjectNameContext
|
|
615
621
|
{
|
|
616
622
|
$client = new PlClient($options);
|
|
617
623
|
return self::ctx([
|
|
618
624
|
'client' => $client,
|
|
619
|
-
'spec' => $
|
|
625
|
+
'spec' => $spec,
|
|
626
|
+
]);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private static function auth_bags(): ProjectNameSpec
|
|
630
|
+
{
|
|
631
|
+
return new ProjectNameSpec(['headers' => [], 'query' => []]);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* `basic: false` is explicit: an HTTP Basic API's generated config carries
|
|
636
|
+
* `auth.basic: true`, and a client that merges it in takes a branch that
|
|
637
|
+
* needs a secret as well. With none supplied that branch deliberately
|
|
638
|
+
* writes nothing, which the probe would read as a public API.
|
|
639
|
+
*/
|
|
640
|
+
private static function auth_block(string $prefix): array
|
|
641
|
+
{
|
|
642
|
+
return ['prefix' => $prefix, 'basic' => false];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Run prepare_auth with both containers present and see which one the
|
|
647
|
+
* generated utility writes to, and under what name. Null means this SDK
|
|
648
|
+
* places no credential at all - a public API - which is a legitimate
|
|
649
|
+
* shape, and the tests below assert exactly that instead. `pair` is the
|
|
650
|
+
* `<scheme>=` lead-in of a COOKIE credential, which rides the header bag
|
|
651
|
+
* under the key `cookie` instead of taking a header of its own.
|
|
652
|
+
*
|
|
653
|
+
* @return array{where:string,name:string,value:mixed,pair:string}|null
|
|
654
|
+
*/
|
|
655
|
+
private static function auth_probe(array $options): ?array
|
|
656
|
+
{
|
|
657
|
+
$ctx = self::auth_ctx($options, self::auth_bags());
|
|
658
|
+
ProjectNamePrepareAuth::call($ctx);
|
|
659
|
+
foreach (['headers', 'query'] as $where) {
|
|
660
|
+
// A cookie credential rides the header bag, because a cookie IS
|
|
661
|
+
// a header.
|
|
662
|
+
$bag = 'query' === $where ? $ctx->spec->query : $ctx->spec->headers;
|
|
663
|
+
foreach ($bag as $name => $value) {
|
|
664
|
+
$pair = '';
|
|
665
|
+
if ('headers' === $where && 'cookie' === $name && is_string($value)
|
|
666
|
+
&& 1 === preg_match(self::COOKIE_PAIR, $value)) {
|
|
667
|
+
$pair = substr($value, 0, -1);
|
|
668
|
+
}
|
|
669
|
+
return ['where' => $where, 'name' => $name, 'value' => $value, 'pair' => $pair];
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** @return array{where:string,name:string,value:mixed,pair:string}|null */
|
|
676
|
+
private static function auth_credential(): ?array
|
|
677
|
+
{
|
|
678
|
+
return self::auth_probe(
|
|
679
|
+
['apikey' => 'K', 'auth' => self::auth_block('Bearer')]);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Every credential this SDK could possibly place: both credentials and
|
|
684
|
+
* Basic switched on, so whichever branch the API has, something lands
|
|
685
|
+
* unless the API is public.
|
|
686
|
+
*
|
|
687
|
+
* @return array{where:string,name:string,value:mixed,pair:string}|null
|
|
688
|
+
*/
|
|
689
|
+
private static function auth_any_credential(): ?array
|
|
690
|
+
{
|
|
691
|
+
return self::auth_probe([
|
|
692
|
+
'apikey' => 'K', 'secret' => 'S',
|
|
693
|
+
'auth' => ['prefix' => 'Bearer', 'basic' => true],
|
|
620
694
|
]);
|
|
621
695
|
}
|
|
622
696
|
|
|
697
|
+
/** @return array{0:mixed,1:bool} the value left in the credential slot, and whether it is there */
|
|
698
|
+
private static function auth_placed(array $options, $seed = null): array
|
|
699
|
+
{
|
|
700
|
+
$cred = self::auth_credential();
|
|
701
|
+
$spec = self::auth_bags();
|
|
702
|
+
if (null !== $cred && null !== $seed) {
|
|
703
|
+
if ('query' === $cred['where']) {
|
|
704
|
+
$spec->query[$cred['name']] = $seed;
|
|
705
|
+
} else {
|
|
706
|
+
$spec->headers[$cred['name']] = $seed;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
$ctx = self::auth_ctx($options, $spec);
|
|
710
|
+
ProjectNamePrepareAuth::call($ctx);
|
|
711
|
+
if (null === $cred) {
|
|
712
|
+
return [null, false];
|
|
713
|
+
}
|
|
714
|
+
$bag = 'query' === $cred['where'] ? $ctx->spec->query : $ctx->spec->headers;
|
|
715
|
+
return [$bag[$cred['name']] ?? null, array_key_exists($cred['name'], $bag)];
|
|
716
|
+
}
|
|
717
|
+
|
|
623
718
|
public function test_prepare_auth_guards_a_missing_spec(): void
|
|
624
719
|
{
|
|
625
|
-
$ctx = self::auth_ctx(['auth' =>
|
|
720
|
+
$ctx = self::auth_ctx(['auth' => self::auth_block(''), 'apikey' => 'K'], null);
|
|
626
721
|
[, $err] = ProjectNamePrepareAuth::call($ctx);
|
|
627
722
|
$this->assertSame('auth_no_spec', self::code($err));
|
|
628
723
|
}
|
|
629
724
|
|
|
630
|
-
|
|
725
|
+
/**
|
|
726
|
+
* Without this the cases below cannot fail for an SDK whose credential the
|
|
727
|
+
* probe misses: every one of them takes the public-API path instead.
|
|
728
|
+
*/
|
|
729
|
+
public function test_prepare_auth_probe_finds_the_credential_this_sdk_places(): void
|
|
631
730
|
{
|
|
632
|
-
$
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
731
|
+
$this->assertSame(
|
|
732
|
+
null === self::auth_credential(), null === self::auth_any_credential());
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
public function test_prepare_auth_places_the_apikey_where_this_api_puts_it(): void
|
|
736
|
+
{
|
|
737
|
+
$cred = self::auth_credential();
|
|
738
|
+
if (null === $cred) {
|
|
739
|
+
// A public API places nothing, and that is the whole assertion.
|
|
740
|
+
[, $has] = self::auth_placed(['apikey' => 'K', 'auth' => self::auth_block('Bearer')]);
|
|
741
|
+
$this->assertFalse($has);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
$this->assertContains($cred['where'], ['headers', 'query']);
|
|
745
|
+
if ('' !== $cred['pair']) {
|
|
746
|
+
// A cookie credential is a `<scheme>=<key>` pair, and the scheme
|
|
747
|
+
// name leaves no room for the option's prefix.
|
|
748
|
+
$this->assertMatchesRegularExpression(self::COOKIE_PAIR, $cred['value']);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
// A header credential is prefix-joined; a query credential is the raw
|
|
752
|
+
// key, because a query parameter has nowhere to put a scheme name.
|
|
753
|
+
$this->assertSame('query' === $cred['where'] ? 'K' : 'Bearer K', $cred['value']);
|
|
636
754
|
}
|
|
637
755
|
|
|
638
756
|
public function test_prepare_auth_a_raw_apikey_goes_in_as_is(): void
|
|
639
757
|
{
|
|
640
|
-
$
|
|
641
|
-
|
|
642
|
-
|
|
758
|
+
[$value, $has] = self::auth_placed(['apikey' => 'K', 'auth' => self::auth_block('')]);
|
|
759
|
+
$cred = self::auth_credential();
|
|
760
|
+
if (null === $cred) {
|
|
761
|
+
$this->assertFalse($has);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
$this->assertSame($cred['pair'] . 'K', $value);
|
|
643
765
|
}
|
|
644
766
|
|
|
645
|
-
public function
|
|
767
|
+
public function test_prepare_auth_an_empty_apikey_drops_the_credential(): void
|
|
646
768
|
{
|
|
647
|
-
$
|
|
648
|
-
['apikey' => '', 'auth' =>
|
|
649
|
-
|
|
650
|
-
);
|
|
651
|
-
ProjectNamePrepareAuth::call($ctx);
|
|
652
|
-
$this->assertArrayNotHasKey('authorization', $ctx->spec->headers);
|
|
769
|
+
[, $has] = self::auth_placed(
|
|
770
|
+
['apikey' => '', 'auth' => self::auth_block('Bearer')], 'stale');
|
|
771
|
+
$this->assertFalse($has);
|
|
653
772
|
}
|
|
654
773
|
|
|
655
|
-
public function
|
|
774
|
+
public function test_prepare_auth_a_public_api_drops_the_credential(): void
|
|
656
775
|
{
|
|
657
|
-
$
|
|
658
|
-
|
|
659
|
-
$this->assertArrayNotHasKey('authorization', $ctx->spec->headers);
|
|
776
|
+
[, $has] = self::auth_placed(['apikey' => 'K'], 'stale');
|
|
777
|
+
$this->assertFalse($has);
|
|
660
778
|
}
|
|
661
779
|
|
|
662
|
-
public function
|
|
780
|
+
public function test_prepare_auth_a_missing_apikey_option_drops_the_credential(): void
|
|
663
781
|
{
|
|
664
|
-
$
|
|
665
|
-
|
|
666
|
-
$this->assertArrayNotHasKey('authorization', $ctx->spec->headers);
|
|
782
|
+
[, $has] = self::auth_placed(['auth' => self::auth_block('Bearer')], 'stale');
|
|
783
|
+
$this->assertFalse($has);
|
|
667
784
|
}
|
|
668
785
|
|
|
669
786
|
|
|
@@ -70,7 +70,8 @@ class PrimaryUtilityTest extends TestCase
|
|
|
70
70
|
* `set` used to pass silently, which defeats the point of a shared
|
|
71
71
|
* oracle. EVERY corpus-backed test goes through here (mirrors ts/py).
|
|
72
72
|
*/
|
|
73
|
-
private function runsection(
|
|
73
|
+
private function runsection(
|
|
74
|
+
string $name, callable $subject, ?callable $adapt = null): void
|
|
74
75
|
{
|
|
75
76
|
$R = self::runpack();
|
|
76
77
|
$spec = $R['spec'];
|
|
@@ -89,6 +90,10 @@ class PrimaryUtilityTest extends TestCase
|
|
|
89
90
|
"add cases, or mark the fixture PENDING in .sdk/test/primary/");
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
if (null !== $adapt) {
|
|
94
|
+
$basic = $adapt($basic);
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
$R['runset']($basic, $subject);
|
|
93
98
|
$this->assertTrue(true, "corpus section '{$name}' completed");
|
|
94
99
|
}
|
|
@@ -111,6 +116,66 @@ class PrimaryUtilityTest extends TestCase
|
|
|
111
116
|
return ($utility->make_context)($ctxmap, $client->get_root_ctx());
|
|
112
117
|
}
|
|
113
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Where this SDK's prepare_auth actually puts the credential: the name is
|
|
121
|
+
* the API's OWN scheme name, not always `authorization`, so it is
|
|
122
|
+
* discovered by running prepare_auth once.
|
|
123
|
+
*
|
|
124
|
+
* @return array{where:string,name:string}|null
|
|
125
|
+
*/
|
|
126
|
+
private static function auth_credential(
|
|
127
|
+
ProjectNameSDK $client,
|
|
128
|
+
ProjectNameUtility $utility
|
|
129
|
+
): ?array {
|
|
130
|
+
$ctx = self::make_test_ctx($client, $utility);
|
|
131
|
+
$ctx->spec = new ProjectNameSpec(['headers' => [], 'query' => []]);
|
|
132
|
+
[, $err] = ($utility->prepare_auth)($ctx);
|
|
133
|
+
// The DEF setup this client is built from carries `auth.basic: false`,
|
|
134
|
+
// so the single-token branch runs and there is a credential to find.
|
|
135
|
+
if ($err !== null) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
foreach (['headers', 'query'] as $where) {
|
|
139
|
+
// A cookie credential rides the header bag, because a cookie IS
|
|
140
|
+
// a header.
|
|
141
|
+
$bag = 'query' === $where ? $ctx->spec->query : $ctx->spec->headers;
|
|
142
|
+
foreach ($bag as $name => $value) {
|
|
143
|
+
return ['where' => $where, 'name' => $name];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Rename the corpus's `headers` bag to the real container, and the
|
|
151
|
+
* `authorization` key inside it to the real credential name. Applied only
|
|
152
|
+
* to the prepareAuth section, so real header assertions elsewhere are
|
|
153
|
+
* untouched.
|
|
154
|
+
*
|
|
155
|
+
* @param array{where:string,name:string} $cred
|
|
156
|
+
*/
|
|
157
|
+
private static function retarget_auth($node, array $cred)
|
|
158
|
+
{
|
|
159
|
+
if (!is_array($node)) {
|
|
160
|
+
return $node;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
$out = [];
|
|
164
|
+
foreach ($node as $key => $value) {
|
|
165
|
+
if ('headers' === $key) {
|
|
166
|
+
$bag = [];
|
|
167
|
+
foreach (is_array($value) ? $value : [] as $bagkey => $bagvalue) {
|
|
168
|
+
$name = 'authorization' === $bagkey ? $cred['name'] : $bagkey;
|
|
169
|
+
$bag[$name] = self::retarget_auth($bagvalue, $cred);
|
|
170
|
+
}
|
|
171
|
+
$out[$cred['where']] = $bag;
|
|
172
|
+
} else {
|
|
173
|
+
$out[$key] = self::retarget_auth($value, $cred);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return $out;
|
|
177
|
+
}
|
|
178
|
+
|
|
114
179
|
private static function make_test_full_ctx(
|
|
115
180
|
ProjectNameSDK $client,
|
|
116
181
|
ProjectNameUtility $utility
|
|
@@ -749,6 +814,19 @@ class PrimaryUtilityTest extends TestCase
|
|
|
749
814
|
$auth_utility = $auth_client->get_utility();
|
|
750
815
|
$utility = self::client()->get_utility();
|
|
751
816
|
|
|
817
|
+
// The corpus writes the credential as `headers.authorization`: a
|
|
818
|
+
// PLACEHOLDER each runner points at the container and name this API
|
|
819
|
+
// actually uses.
|
|
820
|
+
$cred = self::auth_credential($auth_client, $auth_utility);
|
|
821
|
+
$this->assertIsArray($cred,
|
|
822
|
+
'prepare_auth placed no credential in headers or query');
|
|
823
|
+
|
|
824
|
+
$adapt = ('headers' === $cred['where'] && 'authorization' === $cred['name'])
|
|
825
|
+
? null
|
|
826
|
+
: function (array $basic) use ($cred) {
|
|
827
|
+
return self::retarget_auth($basic, $cred);
|
|
828
|
+
};
|
|
829
|
+
|
|
752
830
|
$this->runsection('prepareAuth', function (array $ctxmap) use ($auth_client, $auth_utility, $utility) {
|
|
753
831
|
$ctx = ProjectNameOmni::ctx_from_map($ctxmap, $auth_client, $auth_utility);
|
|
754
832
|
|
|
@@ -759,7 +837,7 @@ class PrimaryUtilityTest extends TestCase
|
|
|
759
837
|
throw $err;
|
|
760
838
|
}
|
|
761
839
|
return null;
|
|
762
|
-
});
|
|
840
|
+
}, $adapt);
|
|
763
841
|
}
|
|
764
842
|
|
|
765
843
|
|